Spring: 全功能栈的应用程序框架2.0

一、Spring的注解支持

1. 为什么使用Spring注解

 可以使用注解进行简化。

2. Spring支持的注解(IoC/DI相关)

        @Repository、@Service、@Controller、@Configuration都是@Component注解的子注解,作用相同。  主要的区别是语义上的区别。当看到不同的注解放在不同层的类中。但是不按照语义去做,非把@Service用在持久层,也是有效果的。但是这样却是不规范的。

注解名称解释
@Component实例化Bean,默认名称为类名首字母变小写(类名不要出现类似AServiceImpl)。支持自定义名称
@Repository@Component子标签,作用和@Component一样,用在持久层。
@Service@Component子标签,作用和@Component一样,用在业务层。
@Controller@Component子标签,作用和@Component一样,用在控制器层。
@Configuration@Component子标签,作用和@Component一样,用在配置类。
@Autowired自动注入。默认byType,如果多个同类型bean,使用byName(默认通过属性名查找是否有同名的bean,也可以通过@Qualifier("bean名称"),执行需要注入的Bean名称)
@Resource非Spring注解。默认byName,如果没找到,使用byType。<dependency> <groupId>javax.annotation</groupId><br/> <artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency>

3. 注解使用

        设置扫描包中的注解,完成对象创建及属性注入。如果需要扫描多个包,包路径之间使用逗号分隔。

 <!-- 多个包使用逗号分隔,下面也可以只写com.bjsxt -->
    <context:component-scan base-package="com.bjsxt.service,com.bjsxt.mapper"></context:component-scan>
</beans>

 @Component注解及子注解使用时,如果没有明确指定bean的名称,默认名称为类名首字母变小写。

@Repository
public class PeopleMapperImpl { //容器中存储 peopleMapperImpl
}

在PeopleService和PeopleService实现类添加下面代码

public interface PeopleService {
    void test();
}
@Service
public class PeopleServiceImpl implements PeopleService { //容器中存储 peopleServiceImpl
    @Autowired //属性值自动注入,先byType后byName
    private PeopleMapperImpl peopleMapper;

    @Override
    public void test() {
        System.out.println(peopleMapper);
    }
}

@Autowired自动注入的注意事项:

底层使用反射直接操作属性完成属性值的自动注入。  

@Service
public class UserServiceImpl {
    @Autowired
    UserMapperImpl userMapper;
}

 底层使用set方法完成属性值的自动注入。

@Service
public class UserServiceImpl {
    UserMapperImpl userMapper;

    @Autowired
    public void setUserMapper(UserMapperImpl userMapper) {
        this.userMapper = userMapper;
    }
}

底层使用构造方法完成属性值的自动注入。

@Service
public class UserServiceImpl {
    UserMapperImpl userMapper;

    public UserServiceImpl() {
    }
    @Autowired
    public UserServiceImpl(UserMapperImpl userMapper) {
        this.userMapper = userMapper;
    }
}

二、Spring Test 模块

整合后可以在测试类中直接使用Spring容器中的内容,把测试类也放入到Spring容器中,测试类里面可以直接使用注解注入容器中的bean对象。

同时也可以通过@ContextConfigration注解指定配置文件路径,让测试方法在启动的时候直接加载配置文件。

 1.引入依赖

  1. spring-context核心依赖无论使用哪个模块都必须导入的。

  2. junit依赖需要导入的,现在使用的就是Spring Test模块整合的junit功能。

  3. spring-test 表示spring test模块的依赖,导入后才有@ContextConfiguration这些注解。

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.16</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.16</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 编写测试

创建com.MyTest(类名不要叫做Test,会和注解@Test冲突。)

当类中有多个@Test的测试方法时,双击方法名,鼠标右键运行。

// 使用Spring整合Junit4的类启动当前测试类
@RunWith(SpringJUnit4ClassRunner.class)
// 启动时加载的配置文件,里面要包含classpath
@ContextConfiguration(locations = "classpath:applicatonContext.xml")
public class MyTest {
    @Autowired
    PeopleService peopleService;

    @Test
    public void test2(){
        peopleService.test();
    }
}

三、代理设计模式

1.引入

对现有的功能方法进行功能升级,就是保留原有功能的基础上增加新的逻辑  

我们可以在A和B插入一个对象C,让A不再直接调用B,而是调用对象C,在C中调用对象B的testB方法,在C对象中调用对象B的方法之前和之后可以添加我们的扩展代码即可!  

 

 代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 客户端不直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。

 

代理设计模式包括:静态代理和动态代理。  

2. 静态代理示例

 创建接口

public interface MyInterface {
    void testMethod();
}

 创建真实对象类喝代理对象类

public class MyTrueClass implements MyInterface{
    @Override
    public void testMethod() {
        System.out.println("我是方法.....");
    }
}


//代理对象
public class MyStaticProxyClass implements MyInterface {
    //代理方法
    @Override
    public void testMethod() {
        //其他代码
        System.out.println("扩展代码上...");
        //调用真实对象的真实方法
        MyTrueClass myTrueClass=new MyTrueClass();
        myTrueClass.testMethod();
        //其他代码
        System.out.println("扩展代码下....");
    }
}

3. 动态代理示例

1.JDK动态代理是基于接口来实现的,被代理的类必须实现了接口。  

public class TestProxy {
    @Test
    public void testJdkProxy(){
        //创建被代理对象
        MyInterfaceJdkImpl myInterfaceJdk = new MyInterfaceJdkImpl();

        //创建代理对象
        /*
         * 参数1: 类加载器。使用指定的类加载器加载动态创建的代理对象。
         * 参数2: 被代理类实现的接口。
         * 参数3: 动态代理对象会自动调用InvocationHandler中的invoke()方法,
         *        需要在invoke方法中编写对应逻辑代码及执行目标对象中的目标方法。
         * */
        MyInterfaceJdk o = (MyInterfaceJdk) Proxy.newProxyInstance(
                TestProxy.class.getClassLoader(),
                new Class[]{MyInterfaceJdk.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        System.out.println("目标方法之前执行");
                        method.invoke(myInterfaceJdk, objects);
                        System.out.println("目标方法之后执行");
                        return null;
                    }
                }
        );
        o.testJdkMethod();
    }
}

 2.Cglig动态是第三方提供的技术,需要导入jar包,并且是基于类的继承。

引入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

 测试类

public class TestProxy {
    @Test
    public void testCglibProxy(){
        //创建增强器,通过增强器获取动态代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyCglib.class); //设置被代理类
        enhancer.setCallback(new MethodInterceptor(){ //动态代理对象会自动调用MethodInterceptor中的intercept()方法。
            @Override                                 //需要在intercept方法中编写对应逻辑代码及执行目标对象中的目标方法。
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("目标方法之前执行");
                methodProxy.invokeSuper(o, objects); //执行目标方法
                System.out.println("目标方法之后执行");
                return null;
            }
        });
        //通过增强器获取动态代理对象
        MyCglib o = (MyCglib) enhancer.create();
        o.testCglibMethod();
    }
}

 总结:

  1. JDK动态代理机制是委托机制,只能对实现了接口的类生成代理,底层通过反射机制实现。

  2. CGLIB动态代理机制是继承机制,针对类生成代理,被代理类和代理类是继承关系,底层通过字节码处理框架asm,修改字节码生成子类。

四、SpringAOP介绍

 

         面向切面编程 (AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。OOP 中模块化的关键单位是类,而 AOP 中模块化的单位是切面。切面能够实现跨越多种类型和对象的关注点(例如事务管理)的模块化。

  1. AOP 叫做面向切面编程。

  2. AOP 是对OOP的补充。

  3. AOP的核心是切面。

  4. AOP是对IoC的补充。

 1.AOP术语

Aspect:切面。为方法添加增强功能的过程。

join point:切入点。就是我们平时说的目标方法,或说对哪个方法做扩展,做增强。

Advice:通知。就是增强内容。

Pointcut:切点。就是表达式(通过切点表达式可以找到需要增强功能的方法),通过表达式说明哪些方法是join point。

AOP Proxy:代理。Spring支持JDK动态代理和cglib动态代理两种方式,可以通过proxy-target-class=true把默认的JDK动态代理修改为Cglib动态代理。

Weaving:织入。织入就是把Advice添加到join point的过程。

 

 什么是AOP?

AOP叫做面向切面编程,属于对OOP的扩展。其实现是基于动态代理设计模式,在IoC基础上实现的。

AOP就是对某个切入点做了通知进行增强扩展,形成横切面。可以实现在不修改原有代码的情况下,做额外扩展。

2. 实现AOP的两种方式

  • Schema-based:所有的通知都需要实现特定类型的接口实现通知。

  • AspectJ:可以使用普通Java类结合特定的配置实现通知。

3. AOP底层代理模式

 SpringAOP底层默认使用的JDK动态代理,但是同时也支持cglib动态代理。

五、Schema-based方式实现AOP

1. 在Schema-based方式中通知的分类

  • 前置通知:在切入点之前执行的增强功能。通知需要实现MethodBeforeAdvice接口。

  • 后置通知:在切入点之后执行的增强功能。通知需要实现AfterReturningAdvice接口。

  • 环绕通知:一个方法包含了前置通知和后置通知的功能。通知需要实现MethodInterceptor接口。

  • 异常通知:如果切入点中出现了异常(绝对不能try...catch解决了异常)就会触发异常通知。通知需要实现ThrowsAdvice接口。

2. 前置通知

 前置通知是在切入点(目标方法)之前执行的增强。

MethodBeforeAdvice接口中必须重写before方法,方法中三个参数:

  • method:切入点方法对象。

  • args:切入点方法参数。

  • target:切入点方法所在的对象

public class MyBefore implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知");
    }
}

配置切面

<!-- 配置前置通知对象 -->
    <bean id="mybefore" class="com.advice.MyBefore"/>
    <!-- aop配置 -->
    <aop:config>
        <!--
            pointcut: 切点,通过切点获取切人点(目标方法)。
                id:切点id
                expression:切入点表达式
                            切入点是固定语法:execution(* 方法的全限定路径带有参数)
                            *:方法返回值类型(* 任意类型)
                            com.service.impl.PeopleServiceImpl.test()        没有参数
                            com.service.impl.PeopleServiceImpl.test(参数类型) 有参数
                            com.service.impl.PeopleServiceImpl.test(..)      不考虑参数
                            com.service.impl.*.*(..)                         通配符
        -->
        <aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.PeopleServiceImpl.test())"/>
        <!--
            advisor:通知,增强内容。织入通知,将增强内容添加到目标方法。
                advice-ref:  引用的通知
                pointcut-ref:引用的切点
        -->
        <aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
    </aop:config>
</beans>

3. 后置通知

后置通知是在切入点之后执行的增强。  

AfterReturningAdvice接口中必须重写afterReturning方法,方法中四个参数:

  • returnValue:返回值

  • method:切入点方法对象

  • args:切入点方法参数

  • target:切入点方法所在的对象

配置切面

   <!-- 配置注解扫描路径 -->
    <context:component-scan base-package="com"/>
	<!-- 配置前置通知对象 -->
    <bean id="mybefore" class="com.advice.MyBefore"/>
  <!-- 配置后置通知对象 -->
    <bean id="myafter" class="com.advice.MyAfter"/>
    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.PeopleServiceImpl.test())"/>
        <aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
        <!-- 织入后置通知 -->
        <aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
    </aop:config>
</beans>

4. 环绕通知

环绕通知可以实现前置通知和后置通知两种功能。

MethodInterceptor接口中必须重写invoke方法,方法中参数:

  • invocation:方法调用器。通过invocation可以proceed()方法调用执行点。

public class MyAround implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕通知-前置增强");
        Object result = invocation.proceed();// 执行切入点
        System.out.println("环绕通知-后置增强");
        return result;
    }
}

 配置切面

  <!-- 配置注解扫描路径 -->
    <context:component-scan base-package="com"/>
    <!-- 配置环绕通知对象 -->
    <bean id="myaround" class="com.advice.MyAround"/>
    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.PeopleServiceImpl.test())"/>
        <!-- 只织入环绕,没有织入前置通知和后置通知。 -->
        <aop:advisor advice-ref="myaround" pointcut-ref="mypoint"/
    </aop:config>
</beans>

5.异常通知

异常通知只有在切入点出现异常时才会被触发。如果方法没有异常,异常通知是不会执行的。  

MethodInterceptor接口没有方法,但是必须严格提供一个下面的方法:

  • public void afterThrowing:必须相同

  • 必须有Exception参数

public class MyThrow implements ThrowsAdvice {
    public void afterThrowing(Exception e){
        System.out.println("异常通知:"+e.getMessage());
    }
}

配置切面

 <!-- 配置异常通知对象 -->
    <bean id="mythrow" class="com.advice.MyThrow"/>
    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.PeopleServiceImpl.test())"/>
        <!-- 织入异常通知 -->
        <aop:advisor advice-ref="mythrow" pointcut-ref="mypoint"/>
    </aop:config>

6. Schema-based方式包含多个通知的执行顺序

如果切面中包含多个通知,执行顺序是按照配置顺序执行。

  • 前置通知:先配置的先执行。

  • 后置通知:先配置的后执行。

六、AspectJ实现AOP

 1. AspectJ方式通知类型

  • 前置通知:before。

  • 后置通知:after。

    • after是否出现异常都执行的后置通知。

    • after-returning切入点不出现异常时才执行的后置通知。

  • 环绕通知:around。

  • 异常通知:after-throwing。

2. 代码实现

         Aspectj方式实现AOP的通知类不需要实现任何的接口,直接声明一个普通java类即可,然后在类中直接定义通知方法即可,方法名随意,但是建议方法名见名知意。

public class MyAdvice {
    //前置通知方法
    public void before(){
        System.out.println("我是前置通知方法...");
    }
    //后置通知方法
    public void after(){
        System.out.println("我是后置通知方法...");
    }
    //环绕通知方法
    public Object round(ProceedingJoinPoint pp) throws Throwable {
        System.out.println("环绕---前");
        //放行
        Object proceed = pp.proceed();
        System.out.println("环绕---后");
        return proceed;
    }
    //异常通知方法
    public  void  myThrow(Exception ex){
        System.out.println("我是异常通知......"+ex.getMessage());
    }
}

配置AOP

  <!-- 配置注解扫描路径 -->
    <context:component-scan base-package="com"/>
    <!-- 创建通知对象 -->
    <bean id="myadvice" class="com.advice.MyAdvice"/>
    <aop:config>
        <!-- 基于Aspectj方式配置 -->
        <aop:aspect ref="myadvice">
                <!-- 切点配置 -->
                <aop:pointcut id="mp" expression="execution(* com.bjsxt.service.*.*(..))"/>
                <!-- 配置前置通知 -->
                <aop:before method="before" pointcut-ref="mp"/>
                 <!-- 配置后置通知(没有异常,执行的后置通知 )-->
                <aop:after-returning method="after" pointcut-ref="mp"/>
                <!-- 配置后置通知(是否出现异常,都会执行的后置通知) -->
                <aop:after method="after" pointcut-ref="mp"/>
                <!-- 配置异常通知 throwing="异常参数名"-->
                <aop:after-throwing method="myThrow" pointcut-ref="mp" throwing="ex"/>
        </aop:aspect>
    </aop:config>

 注意:

        after和after-returning,after无论切点是否出现异常都执行的后置通知,after-returning只有在切点正常执行完成,才会触发的通知。

3. 在通知中获取目标方法的参数

/*
	如果希望获取切入点方法的参数,推荐把通知的参数和切入点的参数写成一致。
*/
public class MyAdvice2 {
    public void mybefore(int id1, boolean bool1){
        System.out.println("前置通知"+id1+","+bool1);
    }

    public void myafter(int id1, boolean bool1){
        System.out.println("后置:"+id1+","+bool1);
    }
    public void myafter2(int id1, boolean bool1){
        System.out.println("后置2:"+id1+","+bool1);
    }

    public Object myaround(ProceedingJoinPoint pjp,int id1, boolean bool1) throws Throwable {
        System.out.println("环绕前置");
        Object result = pjp.proceed();
        System.out.println("环绕后置");
        return result;
    }

    public void mythrow(Exception ex,int id1, boolean bool1){
        System.out.println("异常通知:"+ex.getMessage()+",id:"+id1+",bool1:"+bool1);
    }
}

配置文件

<!-- 配置注解扫描路径 -->
    <context:component-scan base-package="com"/>
	<!-- 配置通知对象 -->
    <bean id="myadvice2" class="com.advice.MyAdvice2"></bean>
    <aop:config>
        <!-- 基于Aspectj方式配置 -->
        <aop:aspect ref="myadvice2">
            <!--
                 切点配置
                 args():编写参数名,参数名称和目标方法中参数名称一致。
            -->
            <aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.PeopleServiceImpl.test(int,boolean)) and args(id1,bool1)"/>
            <aop:before method="mybefore" pointcut-ref="mypoint" arg-names="id1,bool1"/>
            <aop:after-returning method="myafter" pointcut-ref="mypoint" arg-names="id1,bool1"/>
            <aop:after method="myafter2" pointcut-ref="mypoint" arg-names="id1,bool1"/>
            <!-- pjp由Spring自动注入 -->
            <aop:around method="myaround" pointcut-ref="mypoint" arg-names="pjp,id1,bool1"/>
            <aop:after-throwing method="mythrow" pointcut-ref="mypoint" arg-names="ex,id1,bool1" throwing="ex"/>
        </aop:aspect>
    </aop:config>

4. Schema-based和Aspectj的区别

Schema-based:基于接口实现的。每个通知都需要实现特定的接口类型,才能确定通知的类型。由于类已经实现了接口,所以配置起来相对比较简单。尤其是不需要在配置中指定参数和返回值类型。

AspectJ方式:是基于配置实现的。通过不同的配置标签告诉Spring通知的类型。AspectJ方式对于通知类写起来比较简单。但是在配置文件中参数和返回值需要特殊进行配置。

因为Schame-based是运行时增强,AspectJ是编译时增强。所以当切面比较少时,性能没有太多区别。但是当切面比较多时,最好选择AspectJ方式,因为AspectJ方式要快很多。

七、注解方式实现AOP

1. 注解实现

SpringAOP也给出了使用注解方式来配置AOP,但是注解方式只支持替换AspectJ的XML配置。  

<!--配置注解扫描路径-->
    <context:component-scan base-package="com.bjsxt"/>
    <!--配置AOP注解生效-->
    <aop:aspectj-autoproxy expose-proxy="true"/>
  1. 配置Spring容器注解扫描的路径。

  2. 配置AOP注解生效。

@Component

作用:相当于配置文件的bean标签,将某个类的对象扫描到Spring容器中。此注解一般在普通Java类上用。

 默认类名的首字母小写即为bean对象的ID,也可以使用注解的value属性声明自定义的ID,value可以省略不写。

@Component("advice")
public class MyAdvice {
}

@Aspect

作用:声明该类为通知类。

使用:结合@Component在通知类上使用,Spring扫描到容器中并作为通知类。

@Component("advice")
@Aspect
public class MyAdvice {
}

@Service

作用:

相当于配置文件的bean标签,将某个类的对象扫描到Spring容器中。此注解专门在业务层实体类上使用来表明该类为业务类。

注意:

默认类名的首字母小写即为bean对象的ID,也可以使用注解的value属性声明自定义的ID,value可以省略不写。

@Service("us")
public class UserServiceImpl implements UserService {
   ......
}

@pointcut

作用:声明切点。

使用:方法上使用。

@Component("advice")
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.service.*.*(..))")
    public void pointcut(){}
}
@Service("us")
public class UserServiceImpl implements UserService {
    
    @Override
    public void testMethod() {
        System.out.println("我是真实方法...");
    }
}

@Before

作用:声明方法为前置通知方法。

使用:在前置通知方法上声明。

@Component("advice")
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.bjsxt.service.*.*(..))")
    public void p(){}
    
    //前置通知
    @Before("p()")
    public void before(){
       System.out.println("我是前置通知");
    }
}

@After

作用:声明方法为后置通知方法。

使用:在后置通知方法上声明。

//后置通知
@AfterReturning("p()")
public void after(){
    System.out.println("我是后置通知");
}

@Around

作用:声明方法为环绕通知方法。

使用:在环绕通知方法上声明

@Around("p()")
public Object round(ProceedingJoinPoint pp) throws Throwable {
    System.out.println("环绕-前");
    Object proceed = pp.proceed();
    System.out.println("环绕-后");
    return proceed;
}

@AfterThrowing

作用:声明方法为异常通知方法。

使用:在异常通知方法上声明。

@AfterThrowing(value = "p()",throwing = "e")
public void myThrow(Exception e){
    System.out.println("我是异常通知");
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值