Spring

SpringIOC

IOC容器概念

IOC其实就是一个对象的容器。全称Inversion Of Control 控制反转,核心的作用就是将原来由开发人员来控制的对象管理操作交由Spring来管理。

SpringIOC不仅帮我们管理了对象的创建,还包括给对象增加了生命周期行为、作用域(单例、非单例)、懒加载。 配合Spring的DI, 更能方便的解决对象属性值注入、对象之间的依赖注入问题。

IOC容器初始化方式

ClassPathXmlApplicationContext

加载类路径下的xml配置文件的方式,去初始化IOC容器上下文

// 加载单个xml配置,初始化上下文
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");

// 加载多个xml配置,初始化上下文
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"applicationContext1.xml", "applicationContext2.xml"});

// 加载当前运行类所在的类路径下所有以application开头的配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("appliction*.xml");

// 加载工程中所有类路径下所有以application开头的配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:appliction*.xml");

// 加载工程中所有类路径下所有以application或spring开头的配置文件
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath*:appliction*.xml", "classpath*:spring*.xml"});

配置文件路径中可以包含通配符(*)和前缀(classpath*:,代表所有类路径,包括源码类路径和单元测试类路径)

AnnotationConfigApplicationContext

加载通过Java注解方式配置的Bean上下文。

// 加载单个注解配置,初始化上下文
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration.class);

// 加载多个注解配置,初始化上下文
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(new Class[]{MyConfiguration1.class, MyConfiguration2.class});

Bean初始化方式

构造方法方式(最常用)

<bean id="xx" class="com.test.StudentDao" />

静态工厂方法

<!-- 
	class:指定的是静态工厂类,而不是将要创建的对象类型
	factory-method: 指定的是工厂中的静态方法 
-->
<bean id="xx" class="com.test.StudentDaoFactory" factory-method="createDao" />

实例工厂方法

<!-- 
	class:指定的是实例工厂类 
-->
<bean id="xxFactory" class="com.test.StudentDaoFactory" />
<!-- 
	factory-bean:指定的是实例工厂对象
	factory-method: 指定的是工厂中的实例方法 
-->
<bean id="xx" factory-bean="xxFactory" factory-method="createDao" />

Bean的命名

在XML中配置中可以通过标签上的id、name属性值给一个bean命名,以便在其他地方引用。

id属性: bean的唯一名称,只允许出现一个值。且同一个IOC容器中不允许出现两个id值一样的bean。

name属性: 和id类似也是给bean命名。但是name属性的值可以有多个,多个值之间使用英文逗号(,)或者英文分号(;)或者空格符隔开

Bean的作用域

  • prototype

    在SpringIOC中prototype scope的意思指的是非单例,就是每次使用该bean的时候都会重新创建一个对象。

  • singleton(默认)

    singleton作用域是IOC中默认的作用域,代表单例。每次使用bean的时候,不会重新创建对象,在整个IOC容器中该类型的对象只有一个。

Bean的生命周期

  • Singleton Bean的生命周期
    • 初始化时机: 在IOC容器初始化时,就会把配置的所有单例bean实例化。
    • 销毁时机:在IOC容器销毁时,所有bean的销毁方法会被调用。
  • Prototype Bean的生命周期
    • 初始化时机: 在实际使用该bean的时候,比如:getBean、获取依赖此bean的其他bean需要使用
    • 销毁时机: 在IOC容器销毁时。(但是通过destroy-method指定的声明周期方法不会被调用,也就是说Spring不提供prototypebean完整的生命周期管理)
  • 如何指定生命周期的回调方法
    • xml中的init-method、destroy-method
    • 注解方式@PostConstrutor、@PreDestroy
  • 指定默认的声明周期回调方法
    • 在xml中,通过在beans标签上添加default-init-method、default-destory-method来指定
    • 在注解配置中,没有对应的方法可以设置所有bean默认的生命周期回调

Bean懒加载

lazy-init属性

默认是false

懒加载配置主要是针对单例的bean,因为它默认是在容器初始化时就被实例化了。

如何优雅的停止非Web Spring应用

添加一个shutdown hook。所有派生自ConfigurableApplicationContext接口的实现类都支持此方法

ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("xxx.xml");

ctx.registerShutdownHook(); //注册停止回调

Spring DI

概述

DI的全称是Dependency Injection(依赖注入)。IOC是将我们工程中的所有对象交由Spring来管理,DI是此基础,将对象中的属性、依赖的其他对象也管理起来,自动注入到由Spring帮我们管理的对象中。

将要注入的对象和目标对象都必须是由SpringIOC管理的bean.

DI的细节实现

构造参数注入

将一个bean创建过程中构造方法需要的参数,通过Spring DI的方式,自动注入到构造方法中。

Setter注入

先通过一个无参的构造方法创建对象,然后通过属性的setter方法,将属性值注入到对象上。

支持注入的类型

  • 普通字面量

    • String
    • Integer(int)
    • Long(long)
    • Byte(byte)
  • 集和类型

    • List

      <bean id="xxx" class="xx.xxx.xxx.AA"></bean>
      <bean>
          <property name="hobbies">
          	<list>
              	<value>简单类型值</value>
                  <bean>内部bean</bean>
                  <ref bean="xxx" />
              </list>
          </property>
      </bean>
      
    • Map

      <bean id="xxx" class="xx.xxx.xxx.AA"></bean>
      <bean>
          <property name="gameTitle">
          	<map>
              	<entry key="王者荣耀" value="荣耀王者" />
                  <entry key="王者荣耀" value-ref="xxx" />
              </map>
          </property>
      </bean>
      
    • Set

      <bean id="xxx" class="xx.xxx.xxx.AA"></bean>
      <bean>
          <property name="hobbies">
          	<!-- set用法和List类似, 里面可以注入普通字面量值、也可以是一个bean引用,或者内部bean、或者是一个set、list、Properties  -->
              <set>
              	<value>简单类型值</value>
                  <bean>内部bean</bean>
                  <ref bean="xxx" />
              </set>
          </property>
      </bean>
      
      
    • java.util.Properties

      <!-- props标签是用来注入java.util.Properties类型的属性,用法和map类似,但是属性值是在标签中间写 -->
      <property name="gameNick">
          <props>
              <prop key="王者荣耀">最擅长1V5</prop>
              <prop key="吃鸡">一枪爆头</prop>
          </props>
      </property>
      
      
    • 注入空置、空字符串

      <property name="gameNick">
          <null />
      </property>
      
      
      <property name="gameNick" value="" />
      
      

自动装配

自动装配支持的策略
  • byType

    按照类型去IOC容器中找需要的bean,如果找到一个,则自动装配;如果没找到,不注入此属性;如果找到了多个匹配类型的bean,就会报错。

  • byName

    按照名称去IOC容器中找需要的bean,如果找到就自动注入;如果没找到,不注入此属性。

  • constructor

    工作原理和byType类似,也是按照类型去IOC容器中找对应的bean。不同的是注入的地方不是setter,而是构造方法的参数。

  • no (默认值)

    如果没有打开自动注入,默认Spring不会自动装配需要的属性。

XML方式的DI

构造参数注入

  • 常规写法

    <bean id="xx" class="" />
    <bean class="com.test.XXX">
    	<constructor-arg name="age" value="45" />
        <constructor-arg name="x" ref="xx" />  <!-- 注入引用的bean -->
        <constructor-arg name="">	<!-- 注入内部bean -->
        	<bean></bean>
        </constructor-arg>
        <constructor-arg name="">	<!-- 注入list类型构造参数 -->
        	<list>
            	
            </list>
        </constructor-arg>
        ....
    </bean>
    
    
  • c命名空间简写

    <bean id="xx" class="" />
    <bean class="com.test.XXX" c:age="45" c:x-ref="xx" />
    
    

setter方式注入

setter注入能够注入的类型以及写法基本和构造参数注入时的写法一致,只不过将标签换成了

  • 常规用法

    <bean id="xx" class="" />
    <bean class="com.test.XXX">
    	<property name="age" value="45" />
        <property name="x" ref="xx" />  <!-- 注入引用的bean -->
        <property name="">	<!-- 注入内部bean -->
        	<bean></bean>
        </property>
        <property name="">	<!-- 注入list类型构造参数 -->
        	<list>
            	
            </list>
        </property>
        ....
    </bean>
    
    
  • p命名空间简写

    <bean id="xx" class="" />
    <bean class="com.test.XXX" p:age="45" p:x-ref="xx" />
    
    

开启自动装配

<!-- 
通过给当前的bean添加autowire属性开启自动注入
可选的值:参见自动装配章节
 -->
<bean id="xx" class="" autowire="" />

提高自动装配时的权重

<!-- 当其他的bean中需要注入一个Test类型的属性,而满足条件的bean有多个时,会优先注入primary="true"的bean -->
<bean id="xx" class="com.Test" primary="true" />

按类型自动装配时,不参与候选

<!-- 当其他的bean中需要注入一个Test类型的属性,而满足条件的bean有多个时,autowire-candidate="false"的bean会自动退出候选序列 -->
<bean id="xx" class="com.Test" autowire-candidate="false" />

注解方式的DI

  • 构造参数注入

    在构造方法上添加 @Autowired注解,构造方法的参数就会自动注入进来

  • setter方法注入

    方法有两种:

    1. 在属性的setter方法上添加@Autowired注解
    2. 在属性上添加@Autowired注解

提高自动装配时的权重

@Primary
@Component
public class Test {

}

public class Main {
    @Primary
    @Bean
    public void test() {
        return new Test();
    }
}

SpringAOP

概述

AOP的全称是Aspect Oriented Programming(面向切面编程)

OOP语言提供了类与类之间纵向的关系(继承、接口),而AOP补充了横向的关系(比如在不改变目标类中源代码的情况下给com.john.demo.dao包下所有类中以insert和update开头的方法添加事务管理)

SpringAOP和AspectJ的区别

AspectJ是一个专门主打面向切面编程的框架。 它是使用一种特殊的语言(扩展自Java语言)来编写切面代码,后缀是.aj格式,并且需要使用专门的编译器将其编译成jvm可以运行的class文件。

SpringAOP底层也是使用了AspectJ的方案,但是在上层做了很多封装层面的工作,可以让开发人员直接使用Java代码来编写切面。并且由于使用的是标准的Java语言,所以并不需要在额外安装一个专门的编译器。但是由于开发人员直接接触的是Spring AOP,那么凡是Spring中没有实现的那些AOP功能,我们就用不了了,这种情况下只能跟产品经理撕逼或者去学习原生的AspectJ。

AOP的术语

  • 切面(Aspect)

    简单来说,切面就是我们要往目标代码中插入进去的代码。

  • 连接点(Join Pointer)

    理论上所有可能会被切入的地方都可以称之为连接点

  • 切入点(Pointcut)

    选择某个连接点切入,将切面代码织入进去。这个连接点就叫做切入点。

  • 织入(Weaving)

    把切面代码糅合到目标代码中的过程就是织入。

  • 通知(Advice)

    通知决定了切面代码织入到目标代码中后,运行的时机(比如是在目标方法执行前,还是执行后)。

在Spring中使用AOP

基于XML方式使用

  1. 把aop的schema引入

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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">
    
    
  2. 创建一个切面类,并且以bean的方式配置到IOC容器中

    package com.lanou3g.spring;
    public class MyAspect {
        public void wakeup() {
            System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?");
        }
        public void goToBed() {
            System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了");
        }
        public void afterRetuing(Object message) {
            System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message);
        }
        public void afterThrowing(Throwable ex) {
            System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage());
        }
        /**
         * 环绕通知
         * 可以接受一个ProceedingJoinPoint参数
         *      通过此参数可以获取到被切入方法的所有信息
         *      还可以通过此参数来决定是否调用目标方法
         */
        public void aroundAdvice(ProceedingJoinPoint joinPoint) {
            // 连接点参数可以获取到被切入方法的所有信息
            // 这里演示了如何获取被切入方法的名称
            String targetMethodName = joinPoint.getSignature().getName();
            System.out.println("[环绕通知]被切入的方法名:" + targetMethodName);
            //
            System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!");
            try {
                joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!");
        }
    }
    
    
    <bean id="myAspect" class="com.lanou3g.spring.MyAspect" />
    
    

3.使用aop:config标签配置aop(将切面、切入点、通知结合到一起)

  1. 定义切入点表达式
  2. aop:aspect
    1. 引用外部定义的切面bean
    2. 配置通知,引用切入点表达式
   <aop:config>
       <!-- 切入点表示匹配com.lanou3g.spring包下的所有类中所有以oneDay开头的方法,方法的参数、返回值不限 -->
   	<aop:pointcut id="myPointcut" expression="execution(* com.lanou3g.spring..*.oneDay*(..))" />
       <aop:aspect ref="myAspect">
       	<!-- 无论是否出现异常,只要被切入的方法开始运行,都会触发此通知 -->
               <aop:before method="wakeup" pointcut-ref="beforeOneDay" />
               <!-- 无论是否出现异常,只要被切入的方法运行结束,都会触发此通知 -->
               <aop:after method="goToBed" pointcut-ref="beforeOneDay" />
               <!--
               可以最大限度的对被切入方法附加功能,在方法执行前、后都可以通知(无论是否出现异常)
               ,还可以获取到被切入方法的所有信息,包括是否调用被切入的方法
               -->
               <aop:around method="aroundAdvice" pointcut-ref="beforeOneDay" />
               <!-- 被切入的方法正常返回值以后,会触发此通知 -->
               <aop:after-returning method="afterRetuing" pointcut-ref="beforeOneDay" returning="message" />
               <!-- 被切入的方法抛出异常以后,会触发此通知,并且不会触发after-returning -->
               <aop:after-throwing method="afterThrowing" pointcut-ref="beforeOneDay" throwing="ex" />
       </aop:aspect>
   </aop:config>

基于注解方式使用

  1. 开启AOP注解支持

    方式一:注解的方式

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }
    
    

    方式二:xml中开启

    <aop:aspectj-autoproxy/>
    
    
    1. 定义切面类
    /**
     * 该切面用来插入起床的逻辑
     */
    @Aspect
    @Component  //@Aspect注解没有将bean交给ioc容器管理的功能
    public class MyAspect {
        
        @Before("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
        public void wakeup() {
            System.out.println("[前置通知]我刚学习SpringAOP睡着了,刚才谁打了我一下?");
        }
        
        @After("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
        public void goToBed() {
            System.out.println("[后置通知]SpringAOP太难了,一不小心又睡着了");
        }
    
        @AfterReturning(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", returning = "message")
        public void afterRetuing(Object message) {
            System.out.println("[后置返回通知]方法执行已经return了,方法返回值是:" + message);
        }
    
        @AfterThrowing(value = "com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()", throwing = "ex")
        public void afterThrowing(Throwable ex) {
            System.out.println("[后置异常通知]方法执行出现异常,异常原因:" + ex.getMessage());
        }
    
        /**
         * 环绕通知
         * 可以接受一个ProceedingJoinPoint参数
         *      通过此参数可以获取到被切入方法的所有信息
         *      还可以通过此参数来决定是否调用目标方法
         */
    //    @Around("com.lanou3g.spring.aop.MyPointcut.allOneDayMehtod()")
        public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
    
            // 连接点参数可以获取到被切入方法的所有信息
            // 这里演示了如何获取被切入方法的名称
            String targetMethodName = joinPoint.getSignature().getName();
            System.out.println("[环绕通知]被切入的方法名:" + targetMethodName);
    
            //
            System.out.println("[环绕通知]即将开始新的一天, 早起的鸟儿有虫吃!");
            Object ret = null;
            try {
                ret = joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            System.out.println("[环绕通知]这真是充实的一天, 早睡早起,方能养生!");
            return ret;
        }
    }
    
    

    注意:@Aspect注解没有将bean交给ioc容器管理的功能,我们需要额外添加一个@Component注解

  2. 定义切入点

官方建议我们将所有的切入点统一定义到一个地方管理,在配置通知时通过引入的方式来使用。方便后期维护(一处修改,处处生效)

@Component
public class MyPointcut {
    // 通过@Pointcut注解定义一个切入点
    @Pointcut("execution(* oneDay(..))")
    public void allOneDayMehtod() {}
}

  1. 在切面类中添加要切入的代码

参见定义切面部分

  1. 在切入的代码方法上添加通知的注解

参见定义切面部分

动态代理

动态代理特点

字节码随用随创建,随用随加载

它与静态代理的区别也在于此,。因为静态代理是字节码一上来就创建好,并完成加载。

装饰者模式就是静态代理的一种体现。

动态代理常用的有两种方式

  • 基于接口的动态代理

提供者:JDK 官方的 Proxy 类。

要求:被代理类最少实现一个接口。

  • 基于子类的动态代理

提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。

要求:被代理类不能用 final 修饰的类(最终类)。

使用JDK官方的Proxy类创建代理对象

对生产厂家要求的接口

public interface IProducer {
    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money);
    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}

一个生产者

public class Producer implements IProducer{
    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}

模拟一个消费者

public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();
        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于接口的动态代理:
         *      涉及的类:Proxy
         *      提供者:JDK官方
         *  如何创建代理对象:
         *      使用Proxy类中的newProxyInstance方法
         *  创建代理对象的要求:
         *      被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         */
       IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return        和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;

                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

使用CGLib的Enhancer类创建代理对象

消费者

public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于子类的动态代理:
         *      涉及的类:Enhancer
         *      提供者:第三方cglib库
         *  如何创建代理对象:
         *      使用Enhancer类中的create方法
         *  创建代理对象的要求:
         *      被代理类不能是最终类
         *  create方法的参数:
         *      Class:字节码
         *          它是用于指定被代理对象的字节码。
         *
         *      Callback:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
         */
        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}

解决的问题

例:事务处理中每项操作都需要有开启事务,执行业务方法,提交事务,回滚事务,释放资源这几个操作,我们可以把重复代码提取出来

public class BeanFactory {
	/** 
  	* 创建账户业务层实现类的代理对象   
  	* @return   
  	*/  
    public static IAccountService getAccountService() {   
        //1.定义被代理对象   
        final IAccountService accountService = new AccountServiceImpl();   
        //2.创建代理对象   
        IAccountService proxyAccountService = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),        accountService.getClass().getInterfaces(),new InvocationHandler() {        
        /** 
        * 执行被代理对象的任何方法,都会经过该方法。         
        * 此处添加事务控制         
        */        
        @Override        
        public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {         
            Object rtValue = null;         
            try { 
         //开启事务          
                TransactionManager.beginTransaction(); 
         //执行业务层方法          
                rtValue = method.invoke(accountService, args); 
         //提交事务          
                TransactionManager.commit();         
            }catch(Exception e) { 
         //回滚事务          
                TransactionManager.rollback();          
                e.printStackTrace();         
            }finally { 
         //释放资源          
                TransactionManager.release();         
            }         
            return rtValue;        
        } 
      });   
    return proxyAccountService;  
	} 
} 

改造完成后,业务层用于控制事务的重复代码就都可以删掉了

AOP实现

AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类:

  • 静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段通过AOP框架指令生成 AOP 代理类,因此也称为编译时增强;还有一种静态代理是编写代码实现不用工具;这种方式一般是代理模式会使用。
  • 动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值