栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

什么是Spring (详解 IOC和AOP)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

什么是Spring (详解 IOC和AOP)

spring 是目前主流的 Java Web 开发框架,是 Java 世界最为成功的框架。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力,其目的是用于简化企业级应用程序开发的难度和周期。

Spring框架特点:1.方便解耦,简化开发    2.方便集成各种优秀框架    3.降低Java EE API的使用难度    4.方便程序员的测试    5.AOP编程的支持    6.声明式事务的支持

Spring 是分层的 Java SE/EE 一站式轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核。

IOC 指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们使用 new 创建,而使用 Spring 之后,对象的创建都交给了 Spring 框架。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

IOC:控制反转,反转控制权,使用工厂加反射实现解耦

注:项目中 entity 包中的类一般不会纳入spring容器的管理,项目中的组件(servlet service mapper)会放入容器管理。
原因:实体类对应数据库中的表,实体类的重要作用是用于封装持久化层的数据,持久层框架会自动完成对象的创建和数据的封装。

对象初始化方式:

简单类型完成初始化操作:

1.set方法:

初始化对象时,使用property标签,实际在初始化时,会调用类中的对应属性的set方法

set注入 ,类中必须要提供set方法。


     

如果类中有日期类型,默认使用格式2022/7/4,如果使用2022-7-4方式需要自定义数据类型转换器。

首先创建一个类改变日期格式:

  public class MyDateConverter implements Converter {
    @Override
    public Date convert(String source) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date parse = simpleDateFormat.parse(source);
            return parse;
        } catch (ParseException e) {
            e.printStackTrace();
        }

        return null;
    }

  }

然后在配置资源中进行托管,之后将自定义类型的数据转换器交由数据类型转换器工厂对象进行管理:


    

    
        
            
                
                
            
        
    

2.构造方法:

使用构造方法完成对象属性的初始化,  初始化属性的个数要和类中的构造方法相匹配。

方法一:
    
       
    
方法二:
     使用type数据类型完成属性的初始化操作时,如果有相同的数据类型存在,需要配合index指代属性在类中的顺序
 
      
           
 

3.p 命名空间 :property 的简化,需要调用无参构造

作用:简化使用property标签完成初始化的过程 依然要求类中要有set方法,本质还是使用set方式完成初始化

4.c 命名空间 :constructor-arg的简化,使用有参构造完成初始化

  
    
        
        
            
                jack
                tom
                lucy
            
        

        
        
            
                jack
                tom
                lucy
            
        

        
        
            
                jack
                tom
                lucy
            
        

        
        
            
                
                
            
        

        
        
            
                com.mysql.jdbc.Driver
                root
                root
            
        
    


自定义类型的属性完成初始化操作:

     使用ref进行关联,  ref的值是关联bean的id

    
       
    

      进阶:完成属性的自动注入      autowire

autowire="byType 根据实体类中属性类型进行自动注入 spring容器中只能有一个指定类型的bean
autowire="byName 根据实体类中属性名字进行自动注入 根据属性的名字在spring容器中查找和属性名一致的bean的id


获取复杂类型对象的三种方法:

1.实例化工厂对象

 public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:8080/连接数据库的库名?serverTimezone=UTC", "数据库用户名", "连接数据库的密码");
        return connection;
    }

在配置资源里托管创建的类,但是托管的类只能获取工厂对象,还要再另创建一个bean实例使用factory-method="类中方法名" 和factory-bean="托管类的id" 获取connection类型。

2.静态工厂

public static Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:8080/连接数据库的库名?serverTimezone=UTC", "数据库用户名", "数据库连接密码");
        return connection;
    }

静态工厂比实例工厂的类中的方法多了static静态方法关键词,在托管bean中不用再另创一个bean,在该托管bean后加上 factory-method="类中方法名"。

3.实现FactoryBean接口的工厂对象

public class ConnectionInterfaceFactoryBean implements FactoryBean {

    @Override
    public Connection getObject() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:8080/连接数据库的库名?serverTimezone=UTC", "数据库用户名", "数据库连接密码");
        return connection;
    }

    @Override
    public Class getObjectType() {
        return Connection.class;
    }

//    设置返回的实例化对象是不是单例的
    @Override
    public boolean isSingleton() {  //true 单例的bean  false 不是单例的bean
        return false;
    }
}

就是实现FactoryBean接口,重写接口中的方法,泛型指代工厂对象返回值的对象,即Connection。

在配置资源中托管类,返回值不再是ConnectionInterfaceFactoryBean(创建的类名)类型的实例对象,而是getObject()方法的返回值对象。


以上是底层相关,使用bean自己写的部分方法。以后多用注解。

使用注解自动装配,导入context约束,开启spring容器扫描

在各类前加上相关注解,可以全加@Component注解,但为了更好的区分,添加各自相关注解。

 @Repository  dao层
 @Component  组件通用
 @Service    service层
 @Controller 控制层

service业务层中注入的mapper前加@Autowired注解自动注入,该注解默认通过byType类型注入,在注入属性值时,如果发现多余的符合要求的bean实例,那么spring容器会尝试在容器中寻找和属性名一致的id作为注入的值(byName),解决方法:使用@Qualifier()注解指定。

也可以使用@Resource(name="")注解自动注入,和@Autowired效果等同,但它加在set方法上,set方法会执行,@Autowired不会执行set方法。虽然spring支持@Resource的注入,但是@Resource并不是spring注解,需要导入javax.annotation.Resource包,推荐使用@Autowired。

如果没有使用@Autowired注解自动注入,会报空指针错误。


AOP :在不修改源码的基础上实现功能的增强 ,通过代理模式解决 ,分为静态代理和动态代理。动态代理又分为jdk代理和cglib代理,底层默认使用jdk代理,核心是Proxy.newProxyInstance。

静态代理:是基于接口的代理,和业务层实现相同的接口,注入原始类。优点是实现了不修改原码,增强了原始类的功能,实现方式直观明了,但是增加了代码的维护量,后期代码维护非常困难。也就是说业务层接口中加了一个方法,原始类和增强类都要各实现一遍。

动态代理:

jdk动态代理:基于接口的代理

        //原始类
        UserService userService=new UserServiceImpl();
        

        //匿名内部类  业务增强的逻辑代码
        InvocationHandler invocationHandler=new InvocationHandler() {
            

            //实际执行位置
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //添加日志的打印功能
                System.out.println("------jdk log------");
                System.out.println("原始类中被调用方法的名字:"+method.getName());
                System.out.println("调用方法的参数列表:"+ Arrays.toString(args));

                //调用原始类的方法   核心业务
                Object invoke = method.invoke(userService, args);

                return invoke;
            }
        };
        //创建动态代理对象
        UserService usrService =(UserService) Proxy.newProxyInstance(TestSpring.class.getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
//        System.out.println("usrService = " + usrService);
        User user = User.builder().username("tom").password("123").build();
        //进入invoke
        usrService.addUser(user);

先new一个业务层的类,这个类是实现了业务层的接口。

然后创建动态代理对象:

Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler invocationHandler)

动态代理调了方法后实际进入了invoke,在invoke中编写增强的方法。


cglib动态代理:基于父子类关系的代理,没有实现接口的用这个

        UserServiceImpl2 userServiceImpl2 = new UserServiceImpl2();
        Enhancer enhancer = new Enhancer();
        //设置相关参数
        //类加载器
        enhancer.setClassLoader(this.getClass().getClassLoader());
        //设置父类 原始类  没有实现接口
        enhancer.setSuperclass(userServiceImpl2.getClass());
        //Callback 增强的业务  类似jdk中的invoke方法
       MethodInterceptor methodInterceptor=new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("------cglib log------");
                Object invoke = method.invoke(userServiceImpl2, objects);
                return invoke;
            }
        };
        enhancer.setCallback(methodInterceptor);
        //通过enhancer创建代理对象
        UserServiceImpl2 userServiceImpl=(UserServiceImpl2)enhancer.create();

        User user = User.builder().username("tom").password("123").build();

        userServiceImpl.addUser(user);

同样先new一个业务层的类,这个类是不实现类的接口的,再new 一个Enhancer,Enhancer是基于cglib的代理,通过enhancer创建的代理对象调了方法之后转到methodInterceptor,这是写增强的业务,类似jdk代理中的invoke方法。


AOP实现方式:

1.组装切面

切点:通知所添加的位置  类中被添加通知的方法

切面:切点+通知

配置切点:

    
        

   
        
    

expression:切点表达式,类似css选择器,选中需要添加通知的位置

第一个 * 代表 :访问权限修饰符和返回值类型一般都是 * ,第二个 * 代表 :包名+类名+方法名,(..)代表参数列表,不限参数,不限类型,不限个数。

 方法级别:delete
      execution(* delete(..))
 类级别:
      execution(* cn.kgc.spring.service.UserServiceImpl.*(..))
 包级别:
      execution(* cn.kgc.spring.service.*.*(..))

 设置参数类型时:
      如果参数类型是java.lang中的数据类型可以直接写,否则要书写类型的全限定名

例: 


2.通知类,配置切面类中通知的切点

     
     
         
         
             
             

             

             

             

             
             
         
     

在托管的那个类中写了前置通知、后置通知、最终通知、异常通知和环绕通知的相关方法。

前置通知在一个方法执行之前,执行通知。
后置通知在一个方法执行之后,不考虑其结果,执行通知。
返回后通知在一个方法执行之后,只有在方法成功完成时,才能执行通知。
抛出异常后通知在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
环绕通知在一个方法调用之前和之后,执行通知。

环绕通知可以实现其他通知的所有内容,要传入ProceedingJoinPoint切点:

      public Object around(ProceedingJoinPoint proceedingJoinPoint){
        try {
            System.out.println("-------环绕前置通知-------");
            Object proceed = proceedingJoinPoint.proceed();
            System.out.println("-------环绕后置通知-------");
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("-------环绕异常通知-------");
        }finally {
            System.out.println("-------环绕最终通知-------");
        }
        return null;
    }

以上是IOC和AOP的底层实现方法,另外简便的方式还是注解。





    

要在通知类中使用@Aspect注解标记当前类是通知类,其他类要写上spring相关的注解,因为spring容器要先扫描包,当前通知类也要加上@Component注解。

前置通知:@Before("execution(* *(..))")   
后置通知:@AfterReturning("execution(* *(..))")
最终通知:@After("execution(* *(..))")
异常通知:@AfterThrowing("execution(* *(..))")
环绕通知:@Around("execution(* *(..))")

也可以使用切点引入:

@Pointcut("execution(* *(..))")
public void all(){}

将其他通知注解中的execution(* *(..))换成all()

其他通知与环绕通知同时存在时的顺序:

-------环绕前置通知-------
-------前置通知-------
   service的核心业务
-------后置通知-------
-------最终通知-------
-------环绕后置通知-------
-------环绕最终通知-------
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1005940.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号