Spring 使用注解开发、代理模式、AOP

使用注解开发

在Spring4之后,要使用注解开发,必须要保证AOP的包导入了

项目搭建:

  1. 在配置文件中导入约束,增加注解支持

    <?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">
    
        <context:annotation-config/>
    
    </beans>
    
    <?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">
    
        <!--指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="xyz.luck1y.pojo"/>
        <!--注解驱动的包,可以识别spring之外的注解,如前面说的@Resource等等-->
        <context:annotation-config/>
    
    </beans>
    

bean

  • 实体类

    @Component 注解

    package xyz.luck1y.pojo;
    
    import org.springframework.stereotype.Component;
    
    // 等价于xml中的 <bean id="user" class="com.luck1y.pojo.User/>
    // @Component 意为组件,说明这个类已经被Spring管理啦,在xml中配置了组件扫描
    @Component
    public class User {
        public String name = "刘子";
    }
    
  • 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">
    
        <!--指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="xyz.luck1y.pojo"/>
        <!--注解驱动的包,可以识别spring之外的注解,如前面说的@Resource等等-->
        <context:annotation-config/>
    
    </beans>
    
  • 测试:

    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import xyz.luck1y.pojo.User;
    
    public class MyTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.name);
        }
    }
    

属性如何注入

package xyz.luck1y.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 等价于xml中的 <bean id="user" class="com.luck1y.pojo.User/>
// @Component 意为组件,说明这个类已经被Spring管理啦,在xml中配置了组件扫描
@Component
public class User {
    // 相当于<property name="name" value="刘子"/>
    // 如果是很复杂的配置,还是建议使用xml
    @Value("刘子")
    public String name;
    
    
    // 也可以写在set方法上
    @Value("刘子")
    public void setName(String name) {
        this.name = name;
    }
}

衍生的注解

@Component有几个衍生的注解,我们在web开发中,会按照MVC三层架构分层

  • dao层: @Repository 等价于pojo层的@Component

    package xyz.luck1y.dao;
    
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class UserDao {
    }
    
  • service层:@Service 同样等价于pojo层的@Component

    package xyz.luck1y.service;
    
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService {
    }
    
  • controller层:(也就是以前的servlet层)@Controller还是等价于pojo层的@Component

    package xyz.luck1y.controller;
    
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
    }
    

这样写的话,前面的xml配置文件中componment组件扫描范围要扩大:

<context:component-scan base-package="xyz.luck1y"/>

这四个注解功能是一样的,都是代表将某个类注册到Spring容器中,装配bean

作用域

// 单例
@Scope("singleton")
// 原型
@Scope("prototype")

小结

xml和注解:

  • xml更加万能,适用于任何场合!维护简单方便
  • 注解:不是自己的类用不了,无法引用别的类,维护相对复杂

最佳实践:

  • xml用来管理bean
  • 注解用来完成属性的注入
  • 我们在使用的过程中需要注意必须要让注解生效,即在配置文件中开启注解支持

使用Java的方式配置Spring

完全不使用Spring的xml配置,全交给Java来做

JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能~

使用Java配置类来配置Spring

主配置类

package xyz.luck1y.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import xyz.luck1y.pojo.User;

// 相当于扫描包
@ComponentScan("xyz.luck1y.pojo")
// 这个也会被Spring容器托管,注册到容器中,因为它本来就是一个Component,它代表这是一个配置类,就是beans.xml
@Configuration
// 相当于xml中引入其他xml
@Import(MyConfig2.class)
public class MyConfig {
    // 注册一个 bean 就相当于我们之前写的<bean>标签
    // id 标签 就是这个方法的名字
    // class 标签 就是这个方法的返回值类型
    @Bean
    public User getUser(){
        return new User();
    }
}

配置类2

package xyz.luck1y.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig2 {
}

实体类:

package xyz.luck1y.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    @Value("刘子")
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

测试:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import xyz.luck1y.config.MyConfig;
import xyz.luck1y.pojo.User;

public class MyTest {
    public static void main(String[] args) {
        // 如果完全使用了配置类的方式去做,我们就只能通过AnnotationConfigApplicationContext 上下文来获取容器,通过配置类.class获取
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = context.getBean("user", User.class);
        System.out.println(user.getName());
    }
}

其实SpringBoot底层代码都是用以上注解方式写的,为什么说SpringBoot比Spring更强大呢,就是因为它在底层都固定写好了这些注解,拿过来直接用就好了~

代理模式

多线程那块提到过代理模式,这里再来学习一下代理模式

代理模式是SpringAOP的底层,SpringAOP和SpringMVC是面试重点

代理模式的分类:

  • 静态代理
  • 动态代理

静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,会做一系列的附属操作
  • 客户:访问代理对象的人

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注其他的公共业务
  • 公共业务交给代理角色,实现了业务的分工(不需要给每一个房东都加相同的功能,这些相同的功能都提取到代理角色
  • 业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,真实角色变多时,代码量会翻倍,开发效率会变低
  • 为了解决这个问题,便有了动态代理

测试:

  1. 租房接口

    package xyz.luck1y;
    
    // 租房的接口
    public interface Rent {
        public void rent();
    }
    
  2. 房东:真实角色

    package xyz.luck1y;
    
    public class Host implements Rent {
        @Override
        public void rent() {
            System.out.println("房东要出租房子");
        }
    }
    
  3. 中介:代理角色

    package xyz.luck1y;
    
    public class Proxy implements Rent {
        private Host host;
    
        public Proxy(){
    
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        @Override
        public void rent() {
            seeHouse();
            host.rent();
            heTong();
            fee();
        }
    
        // 看房
        public void seeHouse(){
            System.out.println("中介带你看房");
        }
    
        // 签合同
        public void heTong(){
            System.out.println("签署租赁合同");
        }
    
        // 收中介费
        public void fee(){
            System.out.println("收中介费");
        }
    }
    
  4. 客户我们:

    package xyz.luck1y;
    
    public class Client {
    
        public static void main(String[] args) {
            // 房东要租房子
            Host host = new Host();
            // 代理,中介来帮房东租房子,但是中介一般有一些其他操作
            Proxy proxy = new Proxy(host);
            // 你不用面对房东,直接找中介租房就行
            proxy.rent();
    
        }
    }
    

加深理解

  1. 接口:

    package xyz.luck1y.Demo02;
    
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void query();
    }
    
  2. 真实对象:

    package xyz.luck1y.Demo02;
    // 真实对象
    public class UserServiceImpl implements UserService {
        @Override
        public void add() {
            System.out.println("增加了一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除了一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("修改了一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询了一个用户");
        }
    }
    
  3. 代理对象:

    package xyz.luck1y.Demo02;
    
    public class UserServiceProxy implements UserService {
    
        private UserServiceImpl userService;
    
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        @Override
        public void add() {
            log("add");
            userService.add();
        }
    
        @Override
        public void delete() {
            log("delete");
            userService.delete();
        }
    
        @Override
        public void update() {
            log("update");
            userService.update();
        }
    
        @Override
        public void query() {
            log("query");
            userService.query();
        }
    
        // 日志
        public void log(String msg){
            System.out.println("[Debug] 使用了" + msg + "方法");
        }
    }
    
  4. 客户端:

    package xyz.luck1y.Demo02;
    
    public class Client {
        public static void main(String[] args) {
            UserServiceImpl userService = new UserServiceImpl();
            UserServiceProxy userServiceProxy = new UserServiceProxy();
            userServiceProxy.setUserService(userService);
            userServiceProxy.add();
        }
    }
    

为什么不直接在原来的真实对象类加新的内容?

  • 改动原有的业务代码,在工作中是大忌,新增一个类,本来跑的好好的,崩了怎么办~

加一个代理类,可以在原有业务代码不变的基础上进行安全地附加操作

关于AOP

动态代理

  • 动态代理和静态代理的角色一样

  • 动态代理的代理类是动态生成的不是我们直接写好的

  • 动态代理也分为两大类:基于接口的动态代理、基于类的动态代理

    • 基于接口的动态代理【我们在这里使用这种方式】:JDK的动态代理

    • 基于类的动态代理:cglib

    • Java字节码实现:javasist

需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)

  • InvocationHandler:

    一个接口,java.lang.reflect,反射包下

    InvocationHandler是由代理实例的调用处理程序实现的接口,每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

    这个接口只有一个方法:Object invoke(Object proxy , 方法 method , Object[] args) throws Throwable

  • Proxy:

    一个类,java.lang.reflect,反射包下

    Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。也就说说可以通过类来调用方法。

代码实现:

自动生成代理类:

package xyz.luck1y.Demo03;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Rent rent;

    public void setRent(Rent rent){
        this.rent = rent;
    }

    // 生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理的本质就是通过反射机制实现
        log(method.getName());
        seeHose();
        Object result = method.invoke(rent, args);
        getFee();
        return null;
    }

    public void seeHose(){
        System.out.println("中介带着看房子");
    }

    public void getFee(){
        System.out.println("收取中介费用");
    }

    public void log(String msg) {
        System.out.println("[Log] 执行了" + msg + "方法");
    }
}

客户端:

package xyz.luck1y.Demo03;

import xyz.luck1y.Demo02.UserServiceImpl;

public class Client {
    public static void main(String[] args) {
        // 真实角色
        Host host = new Host();
        // 代理角色不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 通过调用程序处理角色来处理我们要调用的接口对象
        pih.setRent(host);
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

房东和租房接口:

package xyz.luck1y.Demo03;

public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子");
    }
}
package xyz.luck1y.Demo03;

// 租房的接口
public interface Rent {
    public void rent();
}

测试结果:

进一步观察:

package xyz.luck1y.Demo04;

import xyz.luck1y.Demo03.Rent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Object target;

    public void setTarget(Object target){
        this.target = target;
    }

    // 生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理的本质就是通过反射机制实现
        log(method.getName());
        seeHose();
        Object result = method.invoke(target, args);
        getFee();
        return null;
    }

    public void seeHose(){
        System.out.println("中介带着看房子");
    }

    public void getFee(){
        System.out.println("收取中介费用");
    }

    public void log(String msg) {
        System.out.println("[Log] 执行了" + msg + "方法");
    }
}
package xyz.luck1y.Demo04;

import xyz.luck1y.Demo02.UserService;
import xyz.luck1y.Demo02.UserServiceImpl;
import xyz.luck1y.Demo03.Host;
import xyz.luck1y.Demo03.Rent;

public class Client {
    public static void main(String[] args) {
        // 真实角色
        UserServiceImpl userService = new UserServiceImpl();
        // 代理角色不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService);
        // 动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        proxy.add();
    }
}

动态代理的优点:

  • 可以使真实角色的操作更加纯粹,不用去关注真实角色的公共业务
  • 公共业务交给代理角色,实现了业务的分工(例子中,不需要给每一个房东都加相同的功能,这些相同的功能都提取到代理角色)
  • 业务发生扩展的时候,方便集中管理
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现类同一个接口即可。

AOP

什么是AOP

AOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是0OP的延续,是软件开发中一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性,同时提高了开发的效率

AOP在Spring中的作用

提供声明式事务,允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或功能,即,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等等
  • 切面(Aspect):横切关注点 被模块化的特殊对象,即,它是一个类
  • 通知(Advice):切面必须要完成的工作,即,它是类中的一个方法
  • 目标(Target):被通知对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • 切入点(PointCut):切面通知执行的“地点”的定义
  • 连接点(JoinPoint):与切入点匹配的执行点

在SpringAOP中:通过Advice定义横切逻辑,Spring支持五种类型的Advice:

即AOP可以在不改变原有代码的情况下,为业务增加新的功能。

使用Spring实现AOP

AOP织入包,需要导入一个依赖包!

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

方式一:使用Spring的API接口

接口:

package xyz.luck1y.service;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void select();
}

实现类:

package xyz.luck1y.service;

public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void select() {
        System.out.println("查询了一个用户");
    }
}

AOP增加日志功能:

package xyz.luck1y.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    // method:要执行的目标对象的方法
    // args:参数
    // target:目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}
package xyz.luck1y.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    // returnValue:返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为:" + returnValue);
    }
}

xml配置文件:

注意在前面写好aop约束

<?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: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">
    <!--注册bean-->
    <bean id="userService" class="xyz.luck1y.service.UserServiceImpl"/>
    <bean id="log" class="xyz.luck1y.log.Log"/>
    <bean id="afterLog" class="xyz.luck1y.log.AfterLog"/>
    
	<!--方式一:使用原生的Spring API接口-->
    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点:expression表达式,execution(要执行的位置! 修饰词 返回值 类名 方法名 参数)  .. 代表有任意的参数-->
        <aop:pointcut id="pointcut" expression="execution(* xyz.luck1y.service.UserServiceImpl.*(..))"/>
        <!--执行环绕增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.luck1y.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContent.xml");
        // 动态代理代理的是接口,此处为 UserService 接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

结果:

方式二:自定义类实现实现AOP

自定义切面插入类:

package xyz.luck1y.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("=============方法执行前=============");
    }

    public void after(){
        System.out.println("=============方法执后=============");
    }
}

配置文件:

<?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: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">
    <!--注册bean-->
    <bean id="userService" class="xyz.luck1y.service.UserServiceImpl"/>
    <bean id="log" class="xyz.luck1y.log.Log"/>
    <bean id="afterLog" class="xyz.luck1y.log.AfterLog"/>
    <!--方式二:自定义类-->
    <!--比第一类简单,但是功能没有第一种强大-->
    <bean id="diy" class="xyz.luck1y.diy.DiyPointCut"/>

    <aop:config>
        <!--自定义切面,ref:要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* xyz.luck1y.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:before method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xyz.luck1y.service.UserService;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContent.xml");
        // 动态代理代理的是接口,此处为 UserService 接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

结果:

方式三:使用注解实现AOP

package xyz.luck1y.diy;
// 方式三:使用注解方式实现AOP

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 标注这个类是一个切面
@Aspect
public class AnnotationPointCut {
    @Before("execution(* xyz.luck1y.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("========方法执行前========");
    }

    @After("execution(* xyz.luck1y.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("========方法执行后========");
    }

    // 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* xyz.luck1y.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("========环绕前========");
        // 获得签名
        Signature signature = joinPoint.getSignature();
        System.out.println("signature:" + signature);
        // 执行方法
        Object proceed = joinPoint.proceed();
        System.out.println("========环绕后========");
        System.out.println(proceed);
    }
}
<?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: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">
    <!--注册bean-->
    <bean id="userService" class="xyz.luck1y.service.UserServiceImpl"/>
    <bean id="log" class="xyz.luck1y.log.Log"/>
    <bean id="afterLog" class="xyz.luck1y.log.AfterLog"/>
    <!--方式三:使用注解-->
    <bean id="annotationPointCut" class="xyz.luck1y.diy.AnnotationPointCut"/>
    <!--开启注解支持   JDK(默认实现)  CGLib-->
    <!--proxy-target-class 设置为 false 为 JDK 实现,true 是 CGLib 实现-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

测试结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/72815.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web framework-Gin

一、Gin Go Web--Go Module 软件框架&#xff08;software framework&#xff09;&#xff0c;通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范&#xff0c;也指为了实现某个软件组件规范时&#xff0c;提供规范所要求之基础功能的软件产品。 框架就是&#…

【广州华锐视点】AR电力职业技能培训系统让技能学习更“智慧”

随着科技的发展&#xff0c;教育方式也在不断地进步和创新。其中&#xff0c;增强现实(AR)技术的出现&#xff0c;为教育领域带来了全新的可能。AR电力职业技能培训系统就是这种创新教学方法的完美实践&#xff0c;它将虚拟与现实相结合&#xff0c;为学生提供了一个沉浸式的学…

Django实现音乐网站 ⑼

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是后台对专辑、首页轮播图原有功能的基础上进行部分功能实现和显示优化。 目录 专辑功能优化 新增编辑 专辑语种改为下拉选项 添加单曲优化显示 新增单曲多选 更新歌手专辑数、专辑单曲数 获取歌手专辑数 保…

FL Studio for Windows-21.1.0.3713中文直装版功能介绍及系统配置要求

FL Studio 21简称FL水果软件,全称是&#xff1a;Fruity Loops Studio编曲&#xff0c;由于其Logo长的比较像一款水果因此&#xff0c;在大家更多的是喜欢称他为水果萝卜&#xff0c;FL studio21是目前最新的版本&#xff0c;这是一款可以让你的计算机就像是一个全功能的录音室&…

kafka 02——三个重要的kafka客户端

kafka 02——三个重要的kafka客户端 1. 前言1.1 关于 Kafka 的安装1.2 常用客户端简介1.3 依赖 2. AdminClient2.1 Admin Configs2.2 AdminClient API2.2.1 设置 AdminClient 对象2.2.2 创建 topic 获取 topic 列表2.2.3 删除topic2.2.4 查看 topic 的描述信息2.2.5 查看 topi…

AMD限制资源用量CU_MASK

通过配置两个环境变量来控制进程所使用的CU&#xff1a; CU_MASK_0 CU_MASK_1 举例&#xff1a; 使用每个ES中的一半CU则配置如下&#xff1a; export CU_MASK_00xcccccccc export CU_MASK_10xcccccccc

Maven进阶2 -- 私服(Nexus)、私服仓库分类、资源上传和下载

目录 私服是一台独立的服务器&#xff0c;用于解决团队内部的资源共享与资源同步问题。 1.Nexus Nexus是sonatype公司的一款maven私服产品。 下载地址 启动 nexus.exe /run nexus 访问 & 登录 2.私服仓库分类 3.资源上传和下载 本地仓库上传和访问资源需要进行配置。…

【高频面试题】JVM篇

文章目录 一、JVM组成1.什么是程序计数器2.什么是Java堆&#xff1f;3.能不能介绍一下方法区(元空间&#xff09;4.你听过直接内存吗5.什么是虚拟机栈6.垃圾回收是否涉及栈内存&#xff1f;7.栈内存分配越大越好吗&#xff1f;8.方法内的局部变量是否线程安全&#xff1f;9.什么…

初识C语言(3)

什么是C语言 1.第一个C语言程序 2.数据类型 3.变量、常量 4.字符串转义字符注释 5.选择语句 6.循环语句 7.函数 8.数组 9.操作符 10.常见关键字 11.define 定义常量和宏 12.指针 13.结构体 这一篇文章我们从常见关键字开始说起&#xff0c;也是…

【设计模式】责任链模式

顾名思义&#xff0c;责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;为请求创建了一个接收者对象的链。这种模式给予请求的类型&#xff0c;对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中&#xff0c;通常每个接收者…

韩国存储芯片顶不住了,加入价格大战,固态硬盘更白菜价了

国产的SSD固态硬盘价格持续下跌&#xff0c;迅速抢占市场&#xff0c;让美韩的存储芯片深感焦虑&#xff0c;之前美光曾跟进一轮&#xff0c;但是由于亏损严重&#xff0c;美光选择了退让&#xff0c;日前韩国第二大存储芯片企业SK海力士选择了跟进&#xff0c;将导致固态硬盘掀…

Linux 终端命令之文件浏览(4) head, tail

Linux 文件浏览命令 cat, more, less, head, tail&#xff0c;此五个文件浏览类的命令皆为外部命令。 hannHannYang:~$ which cat /usr/bin/cat hannHannYang:~$ which more /usr/bin/more hannHannYang:~$ which less /usr/bin/less hannHannYang:~$ which head /usr/bin/he…

痞子衡嵌入式:AppCodeHub - 一站网罗恩智浦MCU应用程序

近日&#xff0c;恩智浦官方隆重上线了应用程序代码中心(Application Code Hub&#xff0c;简称 ACH)&#xff0c;这是恩智浦 MCUXpresso 软件生态的一个重要组成部分。痞子衡之所以要如此激动地告诉大家这个好消息&#xff0c;是因为 ACH 并不是又一个恩智浦官方 github proje…

Scratch 之 3D 画笔程序使用

目录 Part1 摄像头固定的3D效果 Part2 尝试移动摄像头 Part3 边缘裁剪 总结&#xff1a; Part1 摄像头固定的3D效果 首先&#xff0c;我们知道sc中有xy坐标。 现在让我们在sc中引入一个新坐标——z坐标。z轴垂直于电脑屏幕&#xff0c;从屏幕外指向屏幕里。(如下图) z坐标…

【Rust日报】2023-08-11 candle:一个极简的Rust机器学习框架

Bevys Third Birthday Bevy 是一个用 Rust 构建的令人耳目一新的数据驱动的游戏引擎&#xff0c;如果你想学习如何使用 Bevy 制作 2D/3D 游戏、可视化、用户界面或其他图形应用程序&#xff0c;那可以访问Bevy官网去了解更多。 阅读原文&#xff1a;https://bevyengine.org/new…

mysql的相关指令

mysql的相关指令 DML 数据操作语言DQL数据查询 mysql -uroot -p //启动数据库 show databases; //查看有哪些数据库 use 数据库名; //使用某个数据库 show tables; //查看数据库内有哪些表 exit; //退出mysql的命令环境 create database 数据库名称 charset utf8; //创建数据…

【BASH】回顾与知识点梳理(二十三)

【BASH】回顾与知识点梳理 二十三 二十三. Linux 账号管理&#xff08;二&#xff09;23.1 账号管理新增与移除使用者&#xff1a; useradd, 相关配置文件, passwd, usermod, userdelusermoduserdel 23.2 用户功能&#xff08;普通用户可使用&#xff09;idfingerchfnchsh 23.3…

uniapp把城市换成26个字母和城市排序

后端返回的数据 我们要得效果 <template><view><view v-for"(value,key) in cities" :key"key"><view style"color: red;"> {{ key }} </view><view style"border: 1rpx solid black;"><tex…

NLP语言模型概览

语言模型结构分类 Encoder-Decoder&#xff08;Transformer&#xff09;: Encoder 部分是 Masked Multi-Head Self-Attention&#xff0c;Decoder 部分是 Casual Multi-Head Cross-Attention 和 Casual Multi-Head Self-Attention 兼具。比如T5&#xff0c;BART&#xff0c;MA…

【分布式技术专题】RocketMQ延迟消息实现原理和源码分析

痛点背景 业务场景 假设有这么一个需求&#xff0c;用户下单后如果30分钟未支付&#xff0c;则该订单需要被关闭。你会怎么做&#xff1f; 之前方案 最简单的做法&#xff0c;可以服务端启动个定时器&#xff0c;隔个几秒扫描数据库中待支付的订单&#xff0c;如果(当前时间-订…