Zookeeper使用 kubeflow 单例模式 做推广 scipy Yarn class ssis bluetooth menu grep vue请求 后台模板下载 pmp视频教程 jq解析json oracle修改字段默认值 jquery validate mysql连接 python中pop函数 python正则匹配数字 python创建文件 java文件 java学习手册 java环境变量配置 java学习流程 linux系统安装教程图解 linux的安装 linux命令详解词典 华为线刷工具 emit spoonwep 小工具 混沌世界隐藏英雄密码 朋友圈访客记录教程 js包含字符串 碧桂园园宝 网卡驱动安装包 工程地质手册 airdrop是什么 计划任务软件
当前位置: 首页 > 学习教程  > 编程语言

Java代码审计入门基础之Spring

2020/7/24 10:51:55 文章标签:

Java代码审计入门基础之Spring

​ 目前大部分的 Java 互联网项目,都是用 SSM(Spring MVC + Spring + MyBatis)框架组合搭建的,当然现在SpringBoot也很流行,但是学习Java代码审计,先从SSM框架开始学起,这篇文章将会先介绍Spring的基础。

Spring


三层架构
在这里插入图片描述

​ 终于来到Spring框架了,前面讲了Spring MVC和MyBatis框架,现在就讲了Spring框架的作用是什么以及如何在开发中去配置。

​ Spring框架是Java应用最广的框架,是一个轻量级的开源框架,它的主要理念包括 IoC (Inversion of Control,控制反转)AOP(Aspect Oriented Programming,面向切面编程)。Spring框架的结构,见下图。

在这里插入图片描述

  • **Data Access/Integration(持久层):**包含有JDBC、ORM、OXM、JMS和Transaction模块。
  • **Web层:**包含了Web、Web-Servlet、WebSocket、Web-Porlet模块。
  • **AOP模块:**提供了一个符合AOP联盟标准的面向切面编程的实现。
  • **Core Container(IoC):**包含有Beans、Core、Context和SpEL模块。
  • **Test模块:**支持使用JUnit和TestNG对Spring组件进行测试。

Spring框架能实现的功能:

①Spring 能帮我们根据配置文件创建及组装对象之间的依赖关系
②Spring 面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制
③Spring 能非常简单的帮我们管理数据库事务
④Spring 还提供了与第三方数据访问框架(如Hibernate、JPA)无缝集成,而且自己也提供了一套JDBC访问模板来方便数据库访问。
⑤Spring 还提供与第三方Web(如Struts1/2、JSF)框架无缝集成,而且自己也提供了一套Spring MVC框架,来方便web层搭建。
⑥Spring 能方便的与Java EE(如Java Mail、任务调度)整合,与更多技术整合(比如缓存框架)。

Spring框架的意义:

低侵入,方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以
通过 AOP 轻松应付。
声明式事务的支持(基于切面和惯例)
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架( Struts、 Hibernate、 Hessian、Quartz
等)的直接支持。
降低 JavaEE API 的使用难度
Spring对JavaEE API(如 JDBC、 JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的
使用难度大为降低。

IoC(控制反转)

IoC并不是什么技术,而是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。

正控:若要使用某个对象,需要自己去负责对象的创建,即采用new的方式。

反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架

IoC的作用:削减计算机程序中的耦合(即解除我们代码中的依赖关系)。

耦合:程序间的依赖关系,包括类之间的依赖和方法之间的依赖。

解耦:降低程序间的依赖关系。

在实际开发中,应该做到:编译期间不依赖,运行时才依赖。

解耦的思路:

	1、使用反射来创建对象,而避免使用new关键字来创建对象;

	2、通过读取配置文件来获取要创建的对象的全限定类名。

​ 接下来,我将通过自定义一个BeanFactory类(先不使用Spring框架),使用工厂模式的方式去解耦程序,从而讲解IoC的大概原理。但首先,我会先通过传统的方式(使用工厂模式前)写一个demo,然后再通过工厂模式去优化代码。

传统的创建对象方式

先新建一个项目,目录结构如下:

3
​ 在该项目中,我将模拟表现层,通过ui中的类Client来调用业务层service中的接口来模拟保存账户(这里只是模拟保存账户,在console中打印“保存了账户”该信息,并不是真的创建并保存一个账户)。

表现层中的com.nick.ui.Client.java,代码如下:

package com.nick.ui;

import com.service.IAccountService;
import com.nick.service.impl.AccountServiceImpl;

/**
 * 模拟一个表现层,用于调用业务层
 *
 */

public class Client {

    public static void main(String[] args) {

            IAccountService as = new AccountServiceImpl();
            System.out.println(as);
            as.saveAccount();
    }

}

4
业务层中的com.service.IAccountService.java,代码如下:

package com.nick.service;

public interface IAccountService {

    void saveAccount();
}

5
业务层中的实现类com.nick.service.impl.AccountServiceImpl,代码如下:

package com.nick.service.impl;

import com.nick.dao.IAccountDao;
import com.nick.dao.impl.AccountDaoImpl;
import com.nick.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();
    public void saveAccount(){
        accountDao.saveAccount();
    }
}

6
在业务层中通过调用持久层接口的方法来模拟保存账户。

持久层中的com.nick.dao.IAccountDao.java,代码如下:

package com.nick.dao;

public interface IAccountDao {

    void saveAccount();
}

7
持久层中的实现类com.nick.dao.impl.AccountDaoImpl.java,代码如下:

package com.nick.dao.impl;

import com.nick.dao.IAccountDao;

public class AccountDaoImpl implements IAccountDao{

    public void saveAccount(){
        System.out.println("已经保存了账户");
    }
}

8
然后在Client类右键点击运行:

9
运行成功,这里成功“保存了账户”,并打印了业务层对象。

​ 但是,在上面的代码中,业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。 在表现中也依赖了业务层的接口和实现类,见下图(通过new的方式来产生依赖),这种编译期依赖关系,应该在我们开发中杜绝,因此我们需要优化代码来解决。

10
11

使用工厂模式来解决程序耦合

下面,我将对上述代码进行优化,通过工厂模式来减少程序间的耦合。
优化后的项目目录结构如下:

12
首先创建一个BeanFactory类,一个创建bean的工厂,用于创建service和dao对象
方法:

1、需要一个配置文件来配置service和dao

​ 配置内容为:key=value -> 唯一标志名=全限定类名

2、通过读取配置文件中的配置内容,反射创建对象

com.nick.factory.BeanFactory.java,代码如下:

package com.nick.factory;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 *一个创建bean的工厂
 *      用于创建service和dao对象
 *方法:
 *      1、需要一个配置文件来配置service和dao
 *      配置内容为:key=value :唯一标志=全限定类名
 *      2、通过读取配置文件中的配置内容,反射创建对象
 */

public class BeanFactory {

    //定义一个Properties对象
    private static Properties props;

    //定义一个Map,用于存放我们的对象,该Map对象称之为“容器”
    private static Map<String,Object> beans;
    //使用静态代码块为Properties对象赋值
    static {
        try {
            //实例化对象
            props = new Properties();
            //获取Properties文件的流对象
            InputStream in = BeanFactory.class.getClassLoader()
            .getResourceAsStream("bean.properties");
            props.load(in);

            //产生单例对象的方式
            //实例化容器
            beans = new HashMap<String,Object>();
            //取出配置文件中的所有的key
            Enumeration keys = props.keys();
            //遍历枚举中的值
            while(keys.hasMoreElements()){
                //取出每个key
                String key = keys.nextElement().toString();
                //根据key获取bean的路径
                String beanPath = props.getProperty(key);
                //反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                //把key和value存入容器中
                beans.put(key,value);
            }
        }catch (Exception e){
            throw new ExceptionInInitializerError("初始化Properties对象失败");
        }

    }

    /**
     *根据bean的名称获取bean对象
     * @param beanName
     * @return
     */
    //此时获取的对象是单例的
    public static Object getBean(String beanName){
    	//直接通过传入bean的名字来获取容器中的bean对象
        return beans.get(beanName);
    }
}

在resources目录下新建一个bean的配置文件bean.properties:

accountService=com.nick.service.impl.AccountServiceImpl
accountDao=com.nick.dao.impl.AccountDaoImpl

13
接下来,把原来代码中使用new方式引入依赖的两行代码注释掉,改成使用工厂模式的方法,直接去获取容器中所需的对象。

com.nick.service.impl.AccountServiceImpl,代码如下:

package com.nick.service.impl;

import com.nick.dao.IAccountDao;
import com.nick.factory.BeanFactory;
import com.nick.service.IAccountService;

public class AccountServiceImpl implements IAccountService {

    //private IAccountDao accountDao = new AccountDaoImpl();
    IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    public void saveAccount(){
        accountDao.saveAccount();
    }
}

14
com.nick.ui.Client.java,代码如下:

package com.nick.ui;

import com.nick.factory.BeanFactory;
import com.nick.service.IAccountService;

/**
 * 模拟一个表现层,用于调用业务层
 *
 */

public class Client {

    public static void main(String[] args) {
        //测试循环创建的对象是单例还是多例
        for(int i=0;i<5;i++) {
            //IAccountService as = new AccountServiceImpl();
            IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
            System.out.println(as);
            as.saveAccount();
        }
    }
}

15
注:上述代码中的for循环只是为了证明程序中创建的对象是单例而不是多例的,这里可把for循环去掉。

运行结果如下:

16
​ 看到这里,大家可能会有疑问,明明优化前的代码看起来更简洁更简单,反而使用工厂模式后代码量多了,获取对象的方式看起来也更复杂,感觉好像很没必要,这里大家不需要想太多,只需要记住,在实际开发中,我们力求低耦合,程序之间的耦合不可能完全消除的,使用Ioc的方式是为了减少程序之间的耦合,降低对象之间的依赖程度

使用 spring 的 IoC 解决程序耦合

​ 后面,我将会介绍获取IoC容器中bean的两种方式,分别是通过xml文件配置和通过注解的方式。在实际开发中,一般使用注解的方式比较多,下面我就只讲基于注解的IoC配置方式。

基于注解的IoC配置方式

首先,重新创建一个新的Maven项目,然后配置pom.xml中的依赖,Spring依赖的配置如下,使用注解需要用到的jar包是aop包,如下图所示。

17
注意:在配置pom.xml文件的时候,idea的右下角可能会弹出下图的提示,此时只需点击红色箭头指向的地方即可自动引入maven中配置的依赖jar包。

18
该项目使用的例子与上面的项目的例子相似,这里只是使用了Spring IoC中的注解来获取bean对象而已。新项目目录结构如下图:

19
下面开始介绍各个注解:

首先,先上项目的代码:

这里表现层的com.nick.ui.Client.java的代码如下:

package com.nick.ui;

import com.nick.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 模拟一个表现层,用于调用业务层
 *
 */
public class Client {

    public static void main(String[] args) {

        //获取容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //
        IAccountService as = (IAccountService) ac.getBean("accountServiceImpl");

        System.out.println("对象为:"+as);
        //先不使用对象中的方法
        //as.saveAccount();

    }
}

20
持久层的com.nick.dao.IAccountDao.java代码如下:

package com.nick.dao;

public interface IAccountDao {

    void saveAccount();
}

21
持久层的实现类com.nick.dao.AccountDaoImpl.java代码如下:

package com.nick.dao.impl;

import com.nick.dao.IAccountDao;
import org.springframework.stereotype.Component;

@Component
public class AccountDaoImpl implements IAccountDao{

    public void saveAccount(){
        System.out.println("已经保存了账户");
    }
}

22
业务层的com.nick.service.IAccountService.java代码如下:

package com.nick.service;

public interface IAccountService {

    void saveAccount();
}

23
业务层的实现类com.nick.service.AccountServiceImpl.java代码如下:

package com.nick.service.impl;

import com.nick.dao.IAccountDao;
import com.nick.service.IAccountService;
import org.springframework.stereotype.Component;

@Component
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;
    public AccountServiceImpl(){
        System.out.println("成功创建了对象");
    }

    public void saveAccount(){
        accountDao.saveAccount();
    }

}

24
其中,Spring的主配置文件bean.xml的配置如下:

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
    context名称空间和约束中-->
    <context:component-scan base-package="com.nick"></context:component-scan>

</beans>

注:该配置文件的名字不是固定的,但有时候很多人习惯使用applicationContext.xml这个名字。

用于创建对象的注解

它们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的。

  • @Component:

    作用:用于把当前类对象存入spring容器中。

    属性:

    value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。

  • @Controller:一般用在表现层

  • @Service:一般用在业务层

  • @Repository:一般用在持久层

例如,使用@Repository注解:

25
修改代码,执行结果如下:

26
例如,在com.nick.service.AccountServiceImpl.java改为使用@Service注解:

27
​ 以上三个注解他们的作用和属性与@Component是一模一样的,它们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰,所以我们可以代码中的@component注解改成对应层的注解@Controller、@Service和@Repository。

另外需要注意的是,使用@Component注解后,必须在bean.xml配置文件中添加以下配置:

    <context:component-scan base-package="com.nick"></context:component-scan>

而且,在基于注解的IoC配置方式中的配置文件,其中的约束跟基于xml的IoC配置方式使用的约束是不一样的:

基于xml的IoC配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200724093940155.png?x-oss-proc![在这里插入图片描述](https://img-blog.csdnimg.cn/20200724094017397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTQ2NTk2,size_16,color_FFFFFF,t_70)

ess=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNTQ2NTk2,size_16,color_FFFFFF,t_70)

</beans>

基于注解的IoC配置

<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

</beans>

上述代码运行结果如下:

28
在上述代码中,我把Client.java中调用业务层对象的方法那一行的代码注释掉了,下面我把该注释去掉,看看结果会是怎么样:

29
代码运行过程中,报错信息指向了业务层的实现类com.nick.service.AccountServiceImpl.java的18行,这里报错说是空指针异常:

30
由上图代码可见,我们在第12行只定义了对象accountDao,但是这里并没有对该对象初始化,没有为它赋值。这时候我们需要用到一个新的注解@Autowired。

使用@Autowired注解后的com.nick.service.AccountServiceImpl.java代码如下:

31
然后重新运行,这时候成功调用了业务层对象的方法,结果如下:

32
用于注入数据的注解

他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的。

  • @Autowired

    作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。

    如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。

    如果Ioc容器中有多个类型匹配时:

    出现位置:

    可以是变量上,也可以是方法上

若容器中有两个及其以上的bean跟要注入的对象类型一致的情况

为了说明,我在com.nick.dao.impl目录下新建一个AccountDaoImpl2.java文件:

33
同时,也修改原来的业务层的实现类中代码:

34
修改Client.java文件,然后运行结果如下:

35
​ 从上图的报错信息可知,在使用了注解@Autowired进行自动注入的时候,由于同时存在两个类型均为IAccountDao的bean对象,所以不知道注入容器中的哪个bean对象。

​ 这时候,我们通过修改需要被注入的对象变量的名称为注解@Repository中的value值时,发现终于可以注入成功,分别修改两次,然后运行结果如下:

36
37
由上述实验可总结:

​ 自动注入时先匹配类型,若容器中有两个及其以上的bean跟要注入的对象类型一致的话,此时则根据要注入的对象变量的名字跟容器中的bean的id(即用于创建对象的那几个注解中的value值)比较,若名字和id相同则完成注入。

38

  • @Qualifier

    作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用(必须和@Autowired一起使用),但是在给方法参数注入时可以。

    属性:

    value:用于指定注入bean的id(即用于创建对象的那几个注解中的value值)。

    使用@Qualifier注解后的效果如下:

39
​ 此时,即使spring容器中有相同类型的bean,也不会再根据需要被注入对象变量的名字去匹配bean对象了,而是根据@Qualifier中指定的value值去注入相应id的bean对象。

  • @Resource

    作用:通过指定bean的id值,直接根据bean的id注入。它可以独立使用,而不必跟@Autowired一起使用。

    属性:

    name:用于指定bean的id。

40
​ 由上可见,当处理有多个类型相同的bean的对象的时候,使用了@Resource注解就不必同时使用@Autowired和@Qualifier了。

​ 注:以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。

另外,集合类型的注入只能通过XML来实现。

  • @Value

    作用:用于注入基本类型和String类型的数据

    属性:

    value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)

    SpEL的写法:${表达式}

    ​ 好了,到这里我大概介绍了Spring IoC框架中的部分注解,Spring框架中还有很多的其他注解,其具体的用法大家在审计代码的时候遇到了再到网上查询吧。

​ 此时,即使spring容器中有相同类型的bean,也不会再根据需要被注入对象变量的名字去匹配bean对象了,而是根据@Qualifier中指定的value值去注入相应id的bean对象。

  • @Resource

    作用:通过指定bean的id值,直接根据bean的id注入。它可以独立使用,而不必跟@Autowired一起使用。

    属性:

    name:用于指定bean的id。

41
​ 由上可见,当处理有多个类型相同的bean的对象的时候,使用了@Resource注解就不必同时使用@Autowired和@Qualifier了。

​ 注:以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。

另外,集合类型的注入只能通过XML来实现。

  • @Value

    作用:用于注入基本类型和String类型的数据

    属性:

    value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)

    SpEL的写法:${表达式}

    ​ 好了,到这里我大概介绍了Spring IoC框架中的部分注解,Spring框架中还有很多的其他注解,其具体的用法大家在审计代码的时候遇到了再到网上查询吧。

AOP(面向切面编程)

关于AOP的内容我在这里就不讲述了,想了解的可以自己在网上学习。


本文链接: http://www.dtmao.cc/news_show_50351.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?