深入浅出Spring AOP:面向切面编程的实战与解析-程序员宅基地

技术标签: spring  java  开发语言  

导语

Spring AOP(面向切面编程)作为Spring框架的核心特性之一,提供了强大的横切关注点处理能力,使得开发者能够更好地解耦系统架构,将非功能性需求(如日志记录、事务管理、权限控制等)从主业务逻辑中抽离出来,实现模块化的交叉关注点处理。本文将带你逐步探索Spring AOP的关键技术要点及其实战应用。

 

一、AOP基础概念

在Spring AOP中,有几个基础概念对于理解和使用AOP至关重要。以下是对这些概念的详细解释,并配合Java示例代码说明:


1.切面(Aspect)

  • 定义:切面是关注点的模块化,包含了通知(Advice)和切入点(Pointcut)的定义。它是AOP的核心部分,代表了应用中的某个特定关注点,比如事务管理、日志记录等。
   @Aspect
   public class LoggingAspect {

       // 这个类就是一个切面
   }
   

2.连接点(Join Point)

  • 定义:连接点是在程序执行过程中明确的一个点,例如一个方法调用、字段访问等。在Spring AOP中,仅支持方法级别的连接点。
   // 比如,在整个应用中有成千上万个方法调用,每一个都是潜在的连接点
   public class UserService {
       public void addUser(User user) {...}
   }
   

3.切入点(Pointcut)

  • 定义:切入点是一个或多个连接点的集合,定义了哪些连接点将被执行增强。在Spring AOP中,使用 AspectJ 表达式来指定切入点。
   @Pointcut("execution(* com.example.service.*.*(..))")
   public void anyServiceMethod() {}

   // 上述表达式表示所有位于com.example.service包及其子包下的任何公共方法
   

4.通知(Advice)

  • 定义:通知是在特定连接点上执行的操作,它可以是方法级别的拦截器,根据不同时机有不同的类型:
    • 前置通知(Before Advice):在方法执行前执行。
     @Before("anyServiceMethod()")
     public void logBefore(JoinPoint joinPoint) {
         System.out.println("Before executing: " + joinPoint.getSignature().getName());
     }
     
  • 后置通知(After Advice):无论方法是否抛出异常都会执行,但在方法返回结果之后。
    • 返回通知(AfterReturning Advice):在方法成功执行并返回结果后执行。
    • 异常通知(AfterThrowing Advice):在方法抛出异常后执行。
    • 环绕通知(Around Advice):最强大的一种通知,可以控制方法的执行流程,决定方法是否执行,何时执行以及如何执行。
     @Around("anyServiceMethod()")
     public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
         long start = System.currentTimeMillis();
         try {
             Object result = pjp.proceed(); // 执行目标方法
             System.out.println("Method executed in: " + (System.currentTimeMillis() - start) + "ms");
             return result;
         } catch (Throwable ex) {
             System.err.println("Exception caught: " + ex.getMessage());
             throw ex;
         }
     }
     

二、Spring AOP实现机制

Spring AOP 实现机制主要是基于代理模式来实现的。它有两种主要的代理实现方式:JDK动态代理和CGLIB代理。


JDK动态代理
JDK动态代理通过实现java.lang.reflect.Proxy接口来创建代理对象。当目标类实现了至少一个接口时,Spring AOP会优先使用JDK动态代理。


示例代码:

public interface MyService {
    void doSomething();
}

@Service
public class MyServiceImpl implements MyService {
    @Override
    public void doSomething() {
        // 主业务逻辑
    }
}

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.MyService.doSomething(..))")
    public void logBefore() {
        System.out.println("Before method execution");
    }
}

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false) // 默认情况下使用JDK代理
public class AppConfig {
    // ...
}

在这个例子中,MyServiceImpl类实现了MyService接口,Spring AOP通过JDK动态代理为MyService接口创建一个代理对象。当调用doSomething方法时,代理对象会在调用真实方法之前执行LoggingAspect切面中的logBefore方法。

CGLIB代理
当目标类没有实现任何接口时,Spring AOP会选择CGLIB库来生成一个代理子类,扩展自目标类并在其中插入横切逻辑。


示例代码(CGLIB代理需要显式指定):

@Service
public class NonInterfaceService {
    public void doSomething() {
        // 主业务逻辑
    }
}

// 由于NonInterfaceService没有实现接口,Spring AOP将使用CGLIB代理
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // ...
}

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.NonInterfaceService.doSomething(..))")
    public void logBefore() {
        System.out.println("Before method execution");
    }
}

在CGLIB代理的例子中,尽管NonInterfaceService类没有实现任何接口,Spring AOP依然可以通过CGLIB生成它的子类代理,在调用doSomething方法时插入切面逻辑。


工作原理
无论是JDK动态代理还是CGLIB代理,Spring AOP都是通过在代理对象的方法调用时,插入切面逻辑来实现横切关注点的处理。代理对象在运行时“拦截”方法调用,执行对应的切面通知,然后再调用实际的目标方法。这样就达到了在不修改原有业务逻辑代码的情况下,添加通用处理逻辑的目的。

三、Spring AOP API

Spring AOP API主要包括一系列注解和接口,用于定义切面、切入点、通知等。以下是关键API元素的示例代码和详细讲解:


1. 定义切面(Aspect) - 使用@Aspect注解

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class LoggingAspect {

    // 切面内部包含各种通知方法
}

@Aspect注解标记了一个Java类作为切面,表示这个类中包含了一系列与横切关注点相关的通知。


2. 定义切入点(Pointcut) - 使用@Pointcut注解

@Pointcut("execution(* com.example.service.*.*(..))")
public void businessServiceOperation() {}

@Pointcut注解定义了一个切入点表达式,该表达式标识了那些方法调用会被切面影响。上面的表达式匹配了com.example.service包及其子包下所有类的所有方法。


3. 定义通知(Advice) - 使用不同类型的注解

  • 前置通知(Before Advice):在目标方法执行前调用。
@Before("businessServiceOperation()")
public void beforeAdvice(JoinPoint joinPoint) {
    System.out.println("Executing Before advice on method: " + joinPoint.getSignature().getName());
}
  • 后置通知(After Advice):在目标方法执行完后(无论是否有异常)调用,无法访问到方法的返回值。
@After("businessServiceOperation()")
public void afterAdvice(JoinPoint joinPoint) {
    System.out.println("Executing After advice on method: " + joinPoint.getSignature().getName());
}
  • 返回后通知(AfterReturning Advice):在目标方法成功执行并返回后调用,可以访问到方法的返回值。
@AfterReturning(pointcut = "businessServiceOperation()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    System.out.println("Executing AfterReturning advice on method: " + joinPoint.getSignature().getName() + ", Result: " + result);
}
  • 异常抛出通知(AfterThrowing Advice):在目标方法抛出异常后调用,可以访问到抛出的异常。
@AfterThrowing(pointcut = "businessServiceOperation()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
    System.out.println("Executing AfterThrowing advice on method: " + joinPoint.getSignature().getName() + ", Exception: " + exception.getMessage());
}
  • 环绕通知(Around Advice):最强大的通知类型,可以完全控制目标方法的执行,可以选择是否执行目标方法,也可以修改方法的返回值。
@Around("businessServiceOperation()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Starting Around advice on method: " + joinPoint.getSignature().getName());

    try {
        // 前置逻辑
        Object result = joinPoint.proceed(); // 调用目标方法
        
        // 后置逻辑
        System.out.println("Completed Around advice on method, Result: " + result);

        return result;
    } catch (Throwable throwable) {
        // 处理异常逻辑
        System.out.println("Exception thrown from method: " + joinPoint.getSignature().getName());
        throw throwable;
    }
}

4. Spring AOP自动代理
为了使以上切面生效,你需要将此切面类加入Spring容器,并启用AOP代理。在Spring Boot中,通常通过@EnableAspectJAutoProxy注解开启自动代理:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    // ...
}

这样,Spring容器在创建bean时,会为符合条件的bean生成代理对象,当调用代理对象的方法时,就会触发相应的切面通知。

四、配置AOP

Spring AOP可以通过Java注解和XML配置两种方式进行配置。这里我们分别介绍这两种配置方式的示例代码和详细讲解。


1. Java注解方式配置AOP


a. 创建切面类

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void businessServiceMethods() {}

    @Before("businessServiceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        // 前置通知逻辑
    }

    @AfterReturning(pointcut = "businessServiceMethods()", returning = "result")
    public void logAfterReturning(Object result) {
        // 后置通知逻辑
    }

    // 其他通知类型的实现...
}
  • @Aspect 注解表示这是一个切面类。
  • @Component 注解使切面类成为Spring容器中的一个bean。
  • @Pointcut 定义了一个切入点表达式,标识了所有在com.example.service包下定义的方法。
  • @Before 和 @AfterReturning 分别定义了在方法执行前和执行后返回后的通知方法。

 

b. 配置Spring扫描并启用AOP
在Spring Boot应用中,通常只需要添加@EnableAspectJAutoProxy注解在启动类上即可自动扫描带有@Aspect注解的类并启用AOP。

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2. XML配置方式配置AOP


a. 配置切面类

<aop:config>
    <aop:aspect id="loggingAspect" ref="loggingAspectBean">

        <!-- 定义切入点 -->
        <aop:pointcut id="businessServiceMethods"
                      expression="execution(* com.example.service.*.*(..))"/>

        <!-- 前置通知 -->
        <aop:before method="logBefore" pointcut-ref="businessServiceMethods"/>

        <!-- 后置通知 -->
        <aop:after-returning method="logAfterReturning"
                             pointcut-ref="businessServiceMethods"
                             returning="result"/>

        <!-- 其他通知类型的配置... -->
    </aop:aspect>
</aop:config>

<!-- 配置切面类 bean -->
<bean id="loggingAspectBean" class="com.example.aspect.LoggingAspect"/>
  • <aop:config> 标签开始定义AOP配置区域。
  • <aop:aspect> 标签定义一个切面,id属性为其命名,ref属性引用切面类的bean。
  • <aop:pointcut> 定义一个切入点,expression属性内填写切入点表达式。
  • <aop:before> 和 <aop:after-returning> 分别定义前置通知和后置通知,method属性指向切面类中的通知方法,pointcut-ref属性引用前面定义的切入点。

 

b. 配置Spring扫描并启用AOP
在Spring的XML配置文件中,你需要启用AOP名称空间,并确保Spring能够扫描到包含切面类的包。

<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"
       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">

    <!-- 开启AOP自动代理 -->
    <aop:aspectj-autoproxy/>

    <!-- 包扫描配置 -->
    <context:component-scan base-package="com.example"/>

    <!-- 其他配置... -->
</beans>

在这里,<aop:aspectj-autoproxy/> 开启了AOP自动代理功能,而 <context:component-scan> 标签用于指定Spring应该扫描哪些包以查找标注了@Component、@Service等注解的bean,这其中包括我们的切面类。

五、实战应用

Spring AOP 在实战中主要用于处理横切关注点,比如日志记录、事务管理、权限验证、性能统计等。下面给出一个使用Spring AOP进行日志记录的实战应用示例,并详细讲解。
假设有一个简单的服务接口UserService,以及其实现类UserServiceImpl,现在希望在调用服务方法时自动记录日志。


定义服务接口和服务实现

package com.example.service;

public interface UserService {
    void addUser(User user);
    void updateUser(User user);
    void deleteUser(Long id);
}

@Service
public class UserServiceImpl implements UserService {
    // 实现具体的业务逻辑
    @Override
    public void addUser(User user) {
        // 添加用户的逻辑...
    }

    @Override
    public void updateUser(User user) {
        // 更新用户的逻辑...
    }

    @Override
    public void deleteUser(Long id) {
        // 删除用户的逻辑...
    }
}

 

创建日志切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    // 定义切入点,这里是所有`UserService`接口方法的执行
    @Pointcut("execution(* com.example.service.UserService.*(..))")
    public void userServiceMethods() {
    }

    // 定义前置通知,在执行方法前记录日志
    @Before("userServiceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        logger.info("方法名:{},准备执行...", signature.getName());
        // 可以进一步获取方法参数等信息并加入日志
    }
}

在上面的代码中:

  • @Aspect 表明LoggingAspect是一个切面类。
  • @Component 将这个切面类注册为Spring Bean,以便Spring AOP能自动发现和管理。
  • @Pointcut 定义了一个切入点表达式,匹配UserService接口的所有方法。
  • @Before 注解的方法会在符合userServiceMethods切入点条件的方法执行前调用,负责记录日志。

 

通过这种方式,每当调用UserService接口中的任何一个方法时,都会先执行logBefore方法记录日志,然后才执行实际的服务方法。这种做法极大地提高了代码复用性和可维护性,同时降低了侵入性,让业务代码更加清晰简洁。

六、Spring Boot整合AOP

在Spring Boot项目中整合Spring AOP非常简单,因为Spring Boot已经内置了对AOP的支持。下面是一个Spring Boot整合Spring AOP的完整示例,包括创建切面类、定义切入点和通知,以及启动Spring Boot项目时自动启用AOP代理。


Step 1: 添加依赖
在pom.xml文件中,如果使用Maven构建项目,确保已经引入了Spring AOP相关的起步依赖:

<dependencies>
    <!-- Spring Boot Starter Web 或其他组件可能已经包含了此依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

 

Step 2: 创建切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.service..*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        // 方法执行前的逻辑
        System.out.println("方法 " + joinPoint.getSignature().getName() + " 开始执行,时间:" + start);
        
        try {
            // 调用方法并获取返回值
            Object result = joinPoint.proceed();

            // 方法执行后的逻辑
            long end = System.currentTimeMillis();
            System.out.println("方法 " + joinPoint.getSignature().getName() + " 执行结束,耗时:" + (end - start) + " ms");

            return result;
        } catch (IllegalArgumentException e) {
            // 处理自定义异常
            System.err.println("非法参数异常:" + e.getMessage());
            throw e;
        }
    }
}

在上述代码中:

  • 使用@Aspect注解声明LoggingAspect是一个切面类。
  • 使用@Component注解将其注入Spring容器,以便Spring管理其生命周期。
  • @Around注解的方法是一个环绕通知,其表达式execution(* com.example.service..*.*(..))表示切入点,即在com.example.service及其子包下所有类的所有方法执行时触发此通知。


Step 3: 启用AOP代理
在Spring Boot项目中,默认已启用AOP代理,所以通常不需要额外的配置。但如果你在某些特殊情况下需要禁用或定制AOP配置,可以在主配置类或者配置文件中进行调整。
例如,在application.properties中,你可以设置:

spring.aop.auto=true # 默认为true,表示启用AOP代理

或者在主配置类上使用@EnableAspectJAutoProxy注解:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy // 启用AOP代理
public class AppConfig {
    // ...
}

通过以上步骤,当你运行Spring Boot应用并调用com.example.service包下某个服务类的方法时,将会触发LoggingAspect切面类中的logAround方法,在方法执行前后打印日志。这就是Spring Boot整合Spring AOP的基本过程。

七、Spring AOP限制

Spring AOP存在一些内在的技术限制,了解这些限制有助于我们在实际开发中合理地设计和使用AOP。以下是Spring AOP的一些主要限制及相应示例说明:


1.不能拦截final方法:

Spring AOP基于代理机制实现,对于final方法,由于Java语言特性,子类不能覆盖final方法,因此Spring AOP也无法通过代理的方式在其前后增加额外的行为。
示例:

   public final class FinalClass {
       public final void finalMethod() {
           // 主要业务逻辑
       }
   }
   

上述FinalClass中的finalMethod方法无法被Spring AOP所拦截。


2.不能直接代理静态方法:

Spring AOP是基于代理(JDK动态代理或CGLIB)的方式来实现代理功能的,静态方法属于类级别的方法,而非对象实例方法,因此不能通过代理的方式对其进行增强。
示例:

   public class StaticService {
       public static void staticMethod() {
           // 主要业务逻辑
       }
   }
   

上述StaticService中的staticMethod方法无法被Spring AOP所拦截。


3.代理对象内部方法调用的问题:

如果在一个类的内部方法中调用了同一个类的另一个方法,而不是通过代理对象去调用,那么AOP将不会生效,因为此时调用的是实际对象而非代理对象的方法。
示例:

   @Service
   public class SelfCallService {
       
       public void publicMethod() {
           internalMethod(); // 直接内部调用,AOP将不会对此内部调用进行拦截
       }
       
       private void internalMethod() {
           // 主要业务逻辑
       }
   }
   

在此示例中,如果只对publicMethod进行了AOP增强,那么internalMethod的调用将不会受到AOP通知的影响。


4.只能代理Spring管理的bean:

Spring AOP仅能增强那些由Spring IoC容器管理的对象。这意味着非Spring管理的实例,或者通过new关键字直接创建的对象,其方法不会被AOP拦截器处理。


5.CGLIB代理与final类和方法:

虽然CGLIB代理可以代理没有实现接口的类,但它仍然不能代理final类和final方法。


6.构造器注入问题:

由于AOP代理是动态生成的,所以在构造器注入时,注入的将是原始类型而非代理类型。为避免这个问题,推荐使用setter或field注入。


总的来说,Spring AOP在大多数常规应用场景下是非常有效的,但在遇到上述限制时,可能需要考虑更强大的AOP框架如AspectJ,或者重新审视设计,确保业务逻辑适合使用AOP的代理模式。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/2301_78813969/article/details/137111174

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文