Spring IOC与AOP
IOC
1.1 Spring容器
分类:
- Bean工厂,BeanFactory
- 应用上下文,ApplicationContext,常用
获取ApplicationContext:
通过ClassPathXmlApplicationContext对象来获取
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
1.2 创建Bean
Spring中有四种方式配置对象
- 使用XML文件配置
- 使用注解配置
- 使用JavaConfig配置
- groovy脚本DSL
1.2.1 XML文件配置
- 无参构造器创建对象
- 带参构造器创建对象
- 工厂方法创建对象
- 静态工厂创建对象
- 实例工厂创建对象
1.2.1.1 无参构造器创建对象
public Class User {
private String id;
private String username;
}
配置ApplicationContext.xml
<bean id="user" class="User"/>
获取bean
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ac.getBean("user");
1.2.1.2 有参构造器创建对象
JavaBean提供有参构造器
public User(String id, String name, Person person) {
this.id = id;
this.name = name;
this.person = person;
}
配置ApplicationContext.xml
<bean id="person" class="Person"></bean>
<bean id="user" class="User">
<constructor-arg index="0" name="id" type="java.lang.String" value="1"/>
<constructor-arg index="1" name="name" type="java.lang.String" value="Eric"/>
<constructor-arg index="2" name="person" type="Person" ref="person"/>
</bean>
1.2.1.3 静态工厂创建对象
使用静态工厂返回一个对象
public class Factory {
public static User getBean() {
return new User();
}
}
配置文件中使用工厂的静态方法返回对象
<bean id="user" class="Factory" factory-method="getBean">
</bean>
1.2.1.4 实例工厂创建对象
通过工厂类的实例方法获取对象
public class Factory {
public User getBean() {
return new User();
}
}
配置文件中使用工厂的实例方法返回对象
<!-- 先创建工厂对象 -->
<bean id="factory" class="Factroy"></bean>
<!-- 指定工厂对象和工厂方法 -->
<bean id="user" class="User" factory-bean="factory" factory-method="getBean"/>
1.2.2 注解方式创建对象
-
先引入context名称空间
- xmlns:context=“http://www.springframework.org/schema/context”
-
开启注解扫描器
- <context:component-scan base-package=""/>
- 或通过自定义扫描类并以@CompontentScan注解修饰
//表名该类是配置类 @ComponentScan(basePackages = "xxx", useDefaultFilters = true, excludeFilters = {@ConponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)}) public class AnnotationScan { }
-
相关注解
@ComponentScan
@ContextConfiguration
@Configuration
@Component
@Repository
@Service
@Controller
@Resource 依赖关系
- 若@Resource指定了值,就根据名字来查找
- 若@Resource没指定值,就根据类型找,同类型Bean在IOC容器中不能有多个
1.2.3 注解方式创建对象
- 编写Java类,使用@Configuration修饰
- @Configuration 是一个类级别的注解,被@Configuration修饰的类就是配置类,用来标记被修饰类的对象是一个 BeanDefinition。
- 使用@Bean注解声明一个bean
编写配置类
@Configuration
public class Configuration {
}
测试类需用@ContextConfiguration注解修饰
@ContextConfiguration(classes = Configuration.class)
public class Test {
//...
}
1.3 Bean属性
scope属性
-
singleton:单实例,实例在IOC容器创建之前被创建。
-
prototype:多实例,在对象被使用时才会创建
lazy-init属性
仅针对singleton对象有效,默认为false,设置后,对象会在被使用时创建
init-method和destory-method方法
分别为对象创建后和销毁前执行相应方法
1.4 依赖注入
- 构造方法注入
- setter方法注入
- 自动装配
- 注解
AOP
2.1 AOP概念与术语
2.1.1 Aspect(切面)
Aspect由point cut和advice组成,既包含了横切逻辑的定义,也包含了连接点的定义。
- pointcut:匹配join point的谓词,提供一组规则,针对特定的joinpoint织入Advice
- Advice(增强):由Aspect添加到特定join point的一段代码
Advice的类型
- before advice
- after return advice
- after throwing advice
- after(final) advice
- around advice
2.1.2join point(连接点)
2.2 AOP实现方式
基于动态代理
- 基于JDK动态代理
- 基于cglib
2.2.1 基于JDK的动态代理(基于接口实现,Spring AOP默认实现)
-
定义计算器接口
public interface Calculator { int add(int a, int b); }
-
定义计算器接口的实现类
public class MyCalculatorImpl implements Calculator { @Override public int add(int a, int b) { return a + b; } }
-
定义代理类
public class CalculatorProxy { public static Object getInstance(final MyCalculatorImpl myCalculator) { return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() { /** * @param proxy 代理对象 * @param method 代理的方法 * @param args 方法的参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName() + "方法执行将要执行..."); Object invoke = method.invoke(myCalculator, args); System.out.println(method.getName() + "方法执行完毕..."); return invoke; } }); } }
Proxy.newProxyInstance方法的参数:第一个是被代理类的类加载器,第二个是代理对象实现的接口,第三个是代理对象方法的处理器,所有要额外添加的行为都在invoke方法中实现。
2.2.2 cglib动态代理(基于类继承)
代理的类不能为final,否则报错(在内存中构建子类做扩展,被代理类必须被继承)
目标对象的方法如果为final/static,那么不会被拦截
//需要实现的MethodInterceptor接口
public class ProxyFactory implements MethodInterceptor {
//目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建代理对象
public Object getProxyInstance() {
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类代理(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
System.out.println("开始事务...");
//执行目标对象的方法
//Object returnValue = method.invoke(target, args);
proxy.invokeSuper(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
2.4 AOP编程
2.4.1 配置文件中开启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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="aa"/>
<!-- 开启aop注解⽅式 -->
<aop:aspectj-autoproxy/>
</beans>
使用Java Configuration方式使能@AspectJ
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
2.4.2 定义Aspect(切面类)
使用注解@Aspect注解一个Bean后,Spring框架会自动收集这些Bean,并添加到Spring AOP中
@Component
@Aspect//指定为切面类
public class LogAspect {
}
-
注意, 仅仅使用@Aspect 注解, 并不能将一个 Java 对象转换为 Bean, 因此我们还需要使用类似 @Component 之类的注解。
-
并且, 如果一个 类被@Aspect 标注, 则这个类就不能是其他 aspect 的 advised object 了, 因为使用 @Aspect 后, 这个类就会被排除在 auto-proxying 机制之外
4.2.3 声明pointcut
一个 pointcut 的声明由两部分组成:
- 一个方法签名, 包括方法名和相关参数
- 一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice)。
- pointcut方法必须无返回值,方法本身就是 pointcut signature, pointcut 表达式使用@Pointcut 注解指定
在@AspectJ 风格的 AOP 中, 我们使用一个方法来描述 pointcut
//定义pointcut,该方法必须无返回值,这个方法本身就是 pointcut signature, pointcut 表达式使用@Pointcut 注解指定
/**
* 统一定义切点
* 第一个 * 表示要拦截的目标方法返回值为任意
* 第二个 * 表示包中的任意类
* 第三个 * 表示类中的任意方法
* 最后的两个点表示方法参数任意,个数任意,类型任意
*/
@Pointcut("execution(* xxx.*.*(..))")//切点表达式
private void pointcut() {} //切点前面
/**
* @param joinPoint 包含了目标方法的关键信息
* @Before注解表示这是一个前置通知,在目标方法执行之前执行
*/
//⾥⾯的值为切⼊点表达式
@Before(value = "pointcut()")
public void begin(JoinPoint jionPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法开始执行");
}
/**
* @param joinPoint 包含了目标方法的关键信息
* @After注解表示这是一个后置通知,在目标方法执行之后执行
*/
@After(value = "pointcut()")
public void close() {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法执行结束");
}
/**
* @AfterReturning表示返回通知,即目标方法有返回值时才会触发。
* 该注解中的returning属性表示目标方法返回值的变量名,需要和参数一一对应
* 注意:这里方法返回值参数类型要和目标方法返回的类型一致,否则拦截不到
* 如果想拦截所有方法(包括返回值为void),则方法返回值参数可以为Object
*/
@AfterReturning(value = "pointcut()", returning = "r")
public void returning(JointPoint jointPoint, Integer r) {
Signature signature = jointPoint.getSignature();
String name = signature.getName();
System.out.printlin(name + "方法返回" + r);
}
/**
* 异常通知
* @param e 目标方法所抛出的异常,这个参数必须是方法所抛出的异常后置所抛出的异常的父类
* 如果想拦截所有,参数类型声明为Exception
*/
@AfterThrowing(value = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = jointPoint.getSignature();
String name = signature.getName();
System.out.printlin(name + "方法抛出异常" + e.getMessage());
}
/**
* 环绕通知
* 可用通过环绕通知实现上面四个通知,这个方法的核心在于使用类似于反射的机制执行方法
* @param pjp
* @return 注意这里的返回值类型最好是Object,和拦截到的方法相匹配
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//相当于method.invoke方法,可以在这个方法的前后分别添加日志,起到前置/后置通知作用
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
常见的切点表达式:
// 匹配指定包中的所有的方法
execution(* com.xys.service.*(..))
// 匹配当前包中的指定类的所有方法
execution(* UserService.*(..))
// 匹配指定包中的所有 public 方法
execution(public * com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且返回值是 int 类型的方法
execution(public int com.xys.service.*(..))
// 匹配指定包中的所有 public 方法, 并且第一个参数是 String, 返回值是 int 类型的方法
execution(public int com.xys.service.*(String name, ..))
4.2.3 测试
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
MyCalculatorImpl myCalculator = (MyCalculatorImpl) ctx.getBean("MyCalculatorImpl");
myCalculator.add(3, 4);
myCalculator.min(5, 6);
}
}