Spring(2)—自动装配Bean、Spring注解开发、使用java的方式配置Spring、AOP的实现

1、自动装配Bean

  • 自动装配Bean是指Spring会在上下文中自动寻找,来给Bean装配属性
  • Spring由三种装配方式:
    • XML中显示装配
    • java中显式装配
    • 隐式的自动装配Bean

自动装配Bean的测试

(1)环境

public class Cat {
     
    public void shot(){
     
        System.out.println("miao~");
    }
}

public class Dog {
     
    public void shot(){
     
        System.out.println("wang~");
    }
}

@Data
public class People {
     
    private Cat cat;
    private Dog dog;
    private String name;
}
    <bean id="cat" class="com.hdu.pojo.Cat"/>
    <bean id="dog" class="com.hdu.pojo.Dog"/>

    <bean id="people" class="com.hdu.pojo.People">
        <property name="name" value="小王"/>
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    bean>

(2)byName自动装配

    <bean id="cat" class="com.hdu.pojo.Cat"/>
    <bean id="dog" class="com.hdu.pojo.Dog"/>

    <bean id="people" class="com.hdu.pojo.People" autowire="byName">
        <property name="name" value="小王"/>
    bean>
  • byName会在上下文中查找和People类中set方法后面的类名一致的bean
  • 比如setDog方法,自动装配查找的是名称为dog的bean

(3)byType自动装配

    <bean id="cat23" class="com.hdu.pojo.Cat"/>
    <bean id="dog111" class="com.hdu.pojo.Dog"/>

    <bean id="people" class="com.hdu.pojo.People" autowire="byType">
        <property name="name" value="小王"/>
    bean>
  • byType会在上下文中查找,和自己对象属性类型相同的bean
  • 因此虽然名字不同,但可以通过属性的类型来自动装配
byType要求所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致
byName需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法值一致

(4)注解实现自动装配

  • 注解自动装配环境:
    • 导入约束:context约束
    • 配置注解的支持

<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/beans/spring-context.xsd">


    <context:annotation-config/>  
beans>
i、@Autowired、@Qualifier
  • bean的配置
    <bean id="cat" class="com.hdu.pojo.Cat"/>
    <bean id="dog" class="com.hdu.pojo.Dog"/>
    <bean id="people" class="com.hdu.pojo.People"/>
  • 在people类中的属性或者set方法前添加注解即可,前提是符合byName规则
@Data
public class People {
     
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}
  • 配合Qualifier注解使用,可以来定义按照name寻找的bean
@Data
public class People {
     
    @Autowired
    @Qualifier(value = "cat112")
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
}
    <bean id="cat112" class="com.hdu.pojo.Cat"/>
    <bean id="dog" class="com.hdu.pojo.Dog"/>
    <bean id="people" class="com.hdu.pojo.People"/>
ii、java下的注解:@Resource
  • @Resource兼容了byName和byType两种注解形式,如果任意一种符合,就可以完成自动装配

2、Spring注解开发

(1)Spring注解开发的环境

  • 导入aop的包
  • 配置文件中导入context约束,并增加注解的支持

(2)bean

//@Component   等价于    
@Component
@Data
public class User {
     
    private String name;
}

(3)属性注入

@Component
@Data
public class User {
     
    //@Value   等价于   
    @Value("小王")
    private String name;
}

(3)衍生注解

  • Component的一些衍生注解,在web开发中,按照mvc三层架构分层
    • dao 【@Repository】
    • service 【@Service】
    • controller 【@Controller】
  • 这些注解与Component的功能是一致的,只是在不同架构的类中名字不同,用于区分

(4)自动装配

  • 前文中@Autowired、@Qualifier

(5)作用域

@Component
@Data
//@Scope  定义类的作用域  singleton 单例模式   prototype   原型模式
@Scope("singleton")
public class User {
     
    @Value("小王")
    private String name;
}

(6)小结

  • 通过注解的方式将bean配置到Spring容器中,需要指定要扫描的包,包中包含了所有bean,并且需要开启注解支持
    <context:component-scan base-package="com.hdu"/>
    <context:annotation-config/>
  • 通过注解的方式,能够减少xml中bean的管理
  • xml和注解的最佳实践方式是:
    • xml用来管理bean
    • 注解只负责完成属性的注入

3、使用java的方式配置Spring

  • 将xml配置完全交给java来做
  • javaConfig代替XMLConfig
  • java的配置类:
@Configuration//配置类
public class MyConfig {
     
   //注册一个bean,等价于xml文件中的配置方式
   //方法的名字,相当于bean标签中的id
   //方法的返回值,相当于bean标签中的class属性
   @Bean
   public User getUser(){
     
       return new User();//要注入到bean的对象
   }
}
  • 对类添加@Configuration注解,表示将该类作为配置类
  • 测试:
public class MyTest {
     
    public static void main(String[] args) {
     
        //如果使用配置类实现,AnnotationConfig用来获得容器,通过配置类对象加载
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User getUser = (User) context.getBean("getUser");
        System.out.println(getUser.getName());
    }
}
  • 使用配置类需要通过AnnotationConfig来获得容器

4、AOP的实现

(1)AOP的介绍

介绍参考了AOP【面向切面编程】

  • AOP:面向切面编程,即在代码执行过程中,动态嵌入其他代码,常见的使用场景有日志、事物、数据库操作等
  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到著业务逻辑中
  • 交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如日志等。这也与代理模式的实现相近
  • 比如在银行系统取款业务流程和查询业务流程如下:
    Spring(2)—自动装配Bean、Spring注解开发、使用java的方式配置Spring、AOP的实现_第1张图片
  • 两个流程都有一个相同的验证用户的步骤,因此可以通过AOP来简化代码,具体的实现方式是:
    • 业务代码中不写验证用户的步骤,只考虑业务的特有逻辑代码
    • 单独写好验证用户的代码,然后告诉Spring要将这个验证用户的代码放在业务中的那些地方,Spring就会添加过去
    • AOP不会把代码添加到业务的源代码中,但是会影响最终的机器代码
  • 因此,面向切面编程的思想可以概括为:
    Spring(2)—自动装配Bean、Spring注解开发、使用java的方式配置Spring、AOP的实现_第2张图片
  • 将验证用户这个步骤作为一个横向的面,切入到纵向的业务流程中

(2)AOP的相关术语

  • 切面(ASPECT):泛指交叉业务逻辑。比如日志等
  • 织入:将切面代码插入到目标对象的过程
  • 连接点:切面可以织入的位置
  • 切入点:切面具体织入的位置
  • 通知(Advice):切面的一种实现,可以完成简单的织入功能。通知定义了切面切入到目标代码的时间点,是目标方法执行前执行,还是之后执行。通知类型不同,切入时间不同
  • 顾问(Advisor):切面的一种实现,能够将通知以更复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。不仅指定切入时间,还可以指定具体的切入点。

(3)Maven依赖

        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.4version>
        dependency>

AOP的实现实例:
作者:架构师小跟班
出处:http://www.cnblogs.com/xyhero/
个人网站: http://www.jiagou1216.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出,以免更多的人被误导。

(4)AOP的实现方式一:基于XML配置的AOP

  • 采用声明的方式实现(XML中配置),步骤为:
    • 配置文件中配置切入点
    • java中编写实际的切面类
    • 针对切入点进行相关的业务处理

业务接口:

public interface UserService {
     
    public void addUser();
    public Object findUser();
}

业务实现:

public class UserServiceImpl1 implements UserService{
     
    @Value("小王")
    private String name;

    public String getName() {
     
        return name;
    }


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

    @Override
    public void addUser() {
     
        System.out.println("----->执行业务方法addUser");
    }

    @Override
    public String findUser() {
     
        System.out.println("----->执行业务方法findUser,查找到的用户是:"+name);
        return name;
    }
}

切面类

public class Log {
     
    /*
    前置通知:目标方法调用之前执行的代码
     */
    public void doBefore(JoinPoint jp){
     
        System.out.println("======前置通知========");
    }

    /*
    后置返回通知:目标方法正常结束后执行的代码
    返回通知可以访问到目标方法的返回值
     */
    public void doAfterReturning(JoinPoint jp,String result){
     
        System.out.println("======执行后置通知=======");
        System.out.println("======返回值result:"+result);
    }

    /*
    最终通知:目标方法调用之后执行的代码(无论是否出现异常)
    因为方法可能会出现异常,所以不能返回方法的返回值
     */
    public void doAfter(JoinPoint jp){
     
        System.out.println("======执行最终通知======");
    }

    /*
    异常通知:目标方法抛出异常时执行的代码
    可以访问到异常对象
     */
    public void doAfterThrow(JoinPoint jp,Exception ex){
     
        System.out.println("======执行异常通知======");
        System.out.println("======异常:"+ex.toString());
    }

    /*
    环绕通知:目标方法调用前后执行的代码,可以在方法调用前后完成自定义的行为
    保卫一个切入点的通知,会在切入点方法执行前后均执行代码
    主要是调用proceed()方法来执行切入点方法

    环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
    而且环绕通知必须有返回值,返回值即为目标方法的返回值
     */
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
     
        System.out.println("======执行环绕通知开始======");
        //调用方法的参数
        Object[] args=pjp.getArgs();
        //调用方法名
        String method = pjp.getSignature().getName();
        //获取目标对象
        Object target = pjp.getTarget();
        //执行完方法的返回值
        //调用proceed()方法,就会触发切入点方法执行
        Object result = pjp.proceed();
        System.out.println("======方法名:"+method+";目标对象:"+target+";返回值:"+result);
        System.out.println("======执行环绕通知结束");
        return result;
    }
}

Spring配置


<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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.hdu"/>
    <context:annotation-config/>

    <bean id="userService" class="com.hdu.service.UserServiceImpl1"/>
    <bean id="log" class="com.hdu.log.Log"/>
    
    <aop:config>
        <aop:aspect ref="log">
            <aop:pointcut id="pointcut" expression="execution(* com.hdu.service.UserServiceImpl1..*(..))"/>
            <aop:before method="doBefore" pointcut-ref="pointcut"/>
            <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
            <aop:after method="doAfter" pointcut-ref="pointcut"/>
            <aop:around method="doAround" pointcut-ref="pointcut"/>
            <aop:after-throwing method="doAfterThrow" pointcut-ref="pointcut" throwing="ex"/>
        aop:aspect>
    aop:config>


beans>

测试类

public class MyTest {
     
    public static void main(String[] args) {
     
        ApplicationContext context = new ClassPathXmlApplicationContext("aopConfig.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.addUser();
        System.out.println("\n");
        userService.findUser();
    }
}

测试结果

Spring(2)—自动装配Bean、Spring注解开发、使用java的方式配置Spring、AOP的实现_第3张图片

小结

  • 切面类中定义了切入点的各种通知方法,在配置文件中将对应的方法按照对应的配置方式进行定义,需要注意的是afterReturning通知仅在业务方法有返回值时才会调用,如果没有返回值,则该通知不生效
  • 配置文件中切入点的"execution(* com.hdu.service.UserServiceImpl1.*(…))"方法说明:
    • “*” 表示返回值的任意类型
    • com.hdu.service.UserServiceImpl1表示AOP所切的业务部分,可以理解为业务类
    • 业务类后面的“ . ”表示,可以选择业务类中的某些方法作为切入点,同样的“…*”代表所有方法
    • 后面()指传入的参数,(…)指传入任何参数
<aop:pointcut id="pointcut" expression="execution(* com.hdu.service.UserServiceImpl1..*(..))"/>

(4)AOP的实现方式二:基于注解配置AOP

  • 注解实现AOP,业务接口和业务类不改变,将XML配置文件中的切入点、切面、方法的通知类型均以注解形式写在切面类中
注解实现的切面类如下:
//标志这个类是一个切面
@Aspect
public class AnnotationLog {
     
    /*
    必须为final String类型,注解里要使用的变量只能是静态常量型
     */
    public static final String EDP="execution(* com.hdu.service.UserServiceImpl2..*(..))";
    //前置通知
    @Before(EDP)
    public void doBefore(JoinPoint jp){
     
        System.out.println("======前置通知========");
    }

    //返回通知
    @AfterReturning(value = EDP,returning = "result")
    public void doAfterReturning(JoinPoint jp,String result){
     
        System.out.println("======执行后置通知=======");
        System.out.println("======返回值result:"+result);
    }

    //最终通知
    @After(EDP)
    public void doAfter(JoinPoint jp){
     
        System.out.println("======执行最终通知======");
    }

    //异常通知
    @AfterThrowing(value = EDP,throwing = "ex")
    public void doAfterThrow(JoinPoint jp,Exception ex){
     
        System.out.println("======执行异常通知======");
        System.out.println("======异常:"+ex.toString());
    }

    //环绕通知
    @Around(EDP)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
     
        System.out.println("======执行环绕通知开始======");
        //调用方法的参数
        Object[] args=pjp.getArgs();
        //调用方法名
        String method = pjp.getSignature().getName();
        //获取目标对象
        Object target = pjp.getTarget();
        //执行完方法的返回值
        //调用proceed()方法,就会触发切入点方法执行
        Object result = pjp.proceed();
        System.out.println("======方法名:"+method+";目标对象:"+target+";返回值:"+result);
        System.out.println("======执行环绕通知结束");
        return result;
    }
}
配置文件
    
    <context:component-scan base-package="com.hdu"/>
    <context:annotation-config/>
    
    <aop:aspectj-autoproxy/>

    <bean id="userService" class="com.hdu.service.UserServiceImpl2"/>
    <bean id="log" class="com.hdu.log.AnnotationLog"/>

你可能感兴趣的:(spring)