IOC
IOC操作Bean管理(XML方式)
工厂bean(FactoryBean)
一、Spring有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean)
二、普通bean:在配置文件中定义bean类型就是返回类型。例如在bean.xml
中配置对象创建:
<bean id="book" class="com.atguigu.spring5.collectiontype.Book" >
<property name="list" ref="bookList"></property>
</bean>
你这里定义的bean
类型class="com.atguigu.spring5.collectiontype.Book"
就是返回类型,即返回类型是Book
类,我们之前讲的就是普通bean。
三、工厂bean:在配置文件定义bean类型可以和返回类型不一样。假如你定义的bean
类型为class="com.atguigu.spring5.collectiontype.Book"
,即Book
类,但是返回的bean
类型可能是其他类型。工厂bean
应该怎么做:
- 创建类,让这个类作为工厂
bean
,实现接口FactoryBean
,实现接口里面的方法,在实现的方法getObject
中定义返回的bean
类型:新建src/com/atguigu/spring5/factorybean/MyBean.java
文件,代码如下:
public class MyBean implements FactoryBean<Course> {
//定义返回bean,工厂bean返回的类型和定义的类型不一样全是因为在这个方法中定义了返回bean的类型
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
- 配置文件和普通
bean
的一样:<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
- 测试:
TestSpring5Demo1
中的代码如下:
public class TestSpring5Demo1 {
@Test
public void test3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
System.out.println(course);
}
}
bean的作用域
一、在Spring里面,可以设置创建的bean实例是单实例还是多实例,默认情况下,bean是单实例对象
二、如何设置单实例还是多实例:在spring配置文件bean标签里面有属性scope用于设置单实例还是多实例,常用的scope属性值:
singleton
:默认值,表示是单实例对象prototype
:表示是多实例对象
<bean id="book" class="com.atguigu.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>
以上测试代码输出:
三、singleton和prototype区别
- singleton单实例,prototype多实例
- 设置scope值是singleton时,加载spring配置文件时候就会创建单实例对象,即执行
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
这行代码时就创建单实例对象了
设置scope值是prototype时,不是在加载spring配置文件时候创建对象,在调用getBean方法时候创建多实例对象:Course course = context.getBean("myBean", Course.class);
,执行这行代码时创建对象,每次调用时创建的对象都不一样
bean的生命周期
一、生命周期:从对象创建到对象销毁的过程
二、bean生命周期
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化的方法(等下我们需要进行配置初始化的方法)
- bean可以使用了(对象获取到了)
- 当容器关闭时候,调用bean的销毁的方法(等下我们需要进行配置销毁的方法)
三、演示bean生命周期
- 新建
src/com/atguigu/spring5/bean/Orders.java
,在其中定义无参构造函数、属性及属性的set方法、bean的初始化的方法、bean的销毁的方法
public class Orders {
//无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建bean实例");
}
private String oname;
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用set方法设置属性值");
}
//创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
//创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
- 新建
src/bean4.xml
文件,在其中进行配置,注意使用bean
标签的init-method
属性指定bean
的初始化方法initMethod
,destroy-method
属性指定bean
的销毁方法destroyMethod
:
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
- 测试:
TestSpring5Demo1
中代码如下:
public class TestSpring5Demo1 {
@Test
public void testBean3() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
((ClassPathXmlApplicationContext)context).close();
}
}
在销毁bean实例时做了个强转((ClassPathXmlApplicationContext)context).close();
,如果不想做这个强转,可以这样写:
public class TestSpring5Demo1 {
@Test
public void testBean3() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
context.close();
}
}
ClassPathXmlApplicationContext
是ApplicationContext
的子接口,在这里可以使用前者代替后者
四、加上bean的后置处理器,bean的生命周期有七步:
- 通过构造器创建bean实例(无参数构造)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 把bean实例传递bean后置处理器的方法
postProcessBeforeInitialization
- 调用bean的初始化的方法(后面我们需要进行配置初始化的方法)
- 把bean实例传递bean后置处理器的方法
postProcessAfterInitialization
- bean可以使用了(对象获取到了)
- 当容器关闭时候,调用bean的销毁的方法(后面我们需要进行配置销毁的方法)
五、演示添加后置处理器效果
- 创建
src/com/atguigu/spring5/bean/MyBeanPost.java
类,实现接口BeanPostProcessor
,创建后置处理器:
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行的方法");
return bean;
}
}
- 在
bean4.xml
中配置后置处理器:
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"></property>
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
加载配置文件时,他会创建对象和后置处理器,只要类实现了接口BeanPostProcessor
,spring就会把他作为后置处理器执行,后置处理器会给当前配置文件中的所有bean
实例添加后置处理器。执行前面的测试代码后输出:
xml自动装配
一、自动装配:根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入。我们前面向一个类中注入属性时,在配置文件中做如下配置:
<bean id="orders" class="com.atguigu.spring5.bean.Orders" >
<property name="oname" value="手机"></property>
</bean>
我们是通过bean
标签中的property
标签的name
属性、value
属性或ref
属性注入属性的,也就是你手动指定哪个属性注入哪个值,这种方式叫做手动装配
二、演示自动装配过程
- 新建
src/com/atguigu/spring5/autowire
文件夹,在其中新建两个类Dept.java
、Emp.java
,代码分别如下:
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public void test() {
System.out.println(dept);
}
}
- 新建
src/bean5.xml
配置文件,在其中进行自动配置,bean
标签中有个属性autowire
,专门用来配置自动装配。autowire
属性常用两个值:
(1)byName
根据属性名称注入 ,注入值bean
的id
值和类属性名称一样,下面就完成了Emp
类中的dept
属性注入
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
说明:Emp
类的dept
属性名和<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
中bean
的id
值一致
(2)byType
根据属性类型注入
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"></bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
说明:Emp
类的dept
属性的类型Dept
类,spring会在配置文件中找到Dept
类的对象<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
进行注入
注意:在使用byType
根据属性类型注入时,如果类型中有重复的对象,如下面有两个Dept
类的对象,此时spring压根不知道使用dept
还是dept1
,会报错
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"></bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
<bean id="dept1" class="com.atguigu.spring5.autowire.Dept"></bean>
测试:TestSpring5Demo1.java
中代码如下:
public class TestSpring5Demo1 {
@Test
public void test4() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean5.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
}
引入外部属性文件
一、我们之前进行属性注入时,在配置文件中一般做如下配置,有多少个属性就要写多少个property
标签,当类发生变化时,还需要改配置文件,那么我们可以把相关值、固定值放在一个其他文件中,然后将其他文件引入配置文件,配置文件读取其他文件的内容
<bean id="orders" class="com.atguigu.spring5.bean.Orders" >
<property name="oname" value="手机"></property>
</bean>
二、以数据库配置文件为例
- 直接配置数据库信息:
(1)引入连接池依赖jar
包:将下列jar
包复制到lib
文件夹下
引入进来:
新建src/bean6.xml
配置文件,在其中直接配置连接池:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<!--数据库地址-->
<property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>
<!--连接数据库的用户名-->
<property name="username" value="root"></property>
<!--数据库密码-->
<property name="password" value="root"></property>
</bean>
- 引入外部属性文件配置数据库连接池
(1)创建外部属性文件src/jdbc.properties
(注意要是properties
格式文件),写数据库信息
// 为了防止冲突,属性名都加前缀prop.
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.password=root
(2)把外部properties
属性文件引入到spring配置文件中
首先引入context
名称空间
然后使用context:property-placeholder
标签引入外部属性文件,context:property-placeholder
有一个location
属性,用来指定要引入的外部文件的路径,因为jdbc.properties
在src
文件夹下,所以可以写作classpath:jdbc.properties
。最后使用${外部文件的键名}
表达式配置连接池
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
IOC操作Bean管理(注解方式)
创建对象
一、什么是注解
- 注解是代码特殊标记,格式:
@注解名称(属性名称=属性值, 属性名称=属性值..)
- 使用注解,注解作用在类上面,方法上面,属性上面
- 使用注解目的:简化xml配置
二、Spring针对Bean管理中创建对象提供注解,下面四个注解功能是一样的,都可以用来创建bean实例
@Component
:spring容器中提供的普通组件@Service
:用于业务逻辑层或service层@Controller
:用于web层@Repository
:用于dao层或持久层
三、基于注解方式实现对象创建,新建项目spring5_demo3
- 引入依赖:将下面这个jar包复制到
lib
文件夹下
在项目中导入:
- 开启组件扫描,也就是告诉spring我现在在哪个类中要加上注解,让spring去扫描这个类,看类上面有没有注解,有注解就创建对象,即指定spring要扫描的位置
(1)在配置文件中引入名称空间
(2)开启组件扫描:使用context:component-scan
标签的base-package
属性指定扫描哪个包下的内容,其属性值是包名:<context:component-scan base-package="com.atguigu.spring5.dao"></context:component-scan>
。
如果扫描多个包,多个包使用逗号隔开:<context:component-scan base-package="com.atguigu.spring5.dao, com.atguigu.spring5.service"></context:component-scan>
。或者扫描多个包的上层目录,即他们的共同的父目录:<context:component-scan base-package="com.atguigu.spring5"></context:component-scan>
(3)创建类,在类上面添加创建对象注解@Component(value = "这个组件的标识名,相当于bean标签的id属性值")
,value
属性值可以省略不写,省略不写使用默认值,默认值是首字母小写的类名称UserService -- userService
@Component(value = "userService") //<bean id="userService" class=".."/>
public class UserService {
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
- 测试:新建
src/com/atguigu/spring5/testdemo/TestSpring5Demo1.java
文件,测试代码:
public class TestSpring5Demo1 {
@Test
public void testService1() {
ApplicationContext context
= new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
}
四、基于注解方式实现对象创建执行过程:
- 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
- 配置文件中只开启了组件扫描:
<context:component-scan base-package="com.atguigu"></context:component-scan>
,这句话一生效,spring就知道你要用注解方式实现对象创建,他就去你指定的包中找到包中的所有类,若类上面有相关的注解,他就根据注解创建对象。 UserService userService = context.getBean("userService", UserService.class);
对象创建后调用包做输出
组件扫描配置
之前我们使用<context:component-scan base-package="com.atguigu"></context:component-scan>
这种配置,spring会扫描com.atguigu
包下的所有类,但是有些类是没有必要扫的,那么我们可以对组件扫描进行更精细化的配置。
- 给
context:component-scan
标签设置use-default-filters="false"
属性,表示现在不使用默认filter
,自己配置filter
,即不扫描com.atguigu
包下的所有类,扫描com.atguigu
包下我们指定的类。context:include-filter
标签用于设置扫描哪些内容,他的type
属性表明是注解扫描,expression
属性用于指定扫描哪些注解
<!--只扫描com.atguigu包中带Controller注解的类-->
<context:component-scan base-package="com.atguigu" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 使用
<context:component-scan base-package="com.atguigu"></context:component-scan>
配置扫描包所有内容,然后在context:component-scan
标签中使用context:exclude-filter
标签设置哪些内容不进行扫描
<!--不扫描com.atguigu包中带Controller注解的类-->
<context:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
属性注入
新建src/com/atguigu/spring5/dao
文件夹,在其中创建UserDao.java
及其实现类UserDaoImpl.java
,代码如下:
public interface UserDao {
public void add();
}
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao add.....");
}
}
@Autowired:根据属性类型进行自动装配
我们现在想实现在service
中调用dao
中的方法:
- 创建
service
和dao
对象,在service
和dao
类添加创建对象注解:在service
的类上添加@Service
注解,在UserDaoImpl
上添加@Repository
注解 - 在
service
注入dao
对象,在service
类添加dao
类型属性,在属性上面使用注解
(1)在属性上面使用@Autowired
根据类型进行注入:
@Service
public class UserService {
//定义dao类型属性,不需要添加set方法,添加注入属性注解
@Autowired //根据类型进行注入
private UserDao userDao;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
@Qualifier:根据名称进行注入
一个类型有很多个实现类,此时用@Autowired
根据类型注入就没法注入,spring不知道用哪个实现类,现在我们使用@Qualifier
根据名称进行注入:@Qualifier(value = "实现类的注入名称")
- 在
UserDaoImpl
类上使用@Repository
注解设置组件标识名,相当于bean
标签的id
属性值,不设置则使用默认值,默认值是首字母小写的类名称
@Repository(value = "userDaoImpl1")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao add.....");
}
}
- 在
UserService
中使用@Qualifier
注解进行注入,在@Qualifier
中使用value
属性指定组件标识名,也就是说@Qualifier
中的value属性值和@Repository
中的value
属性值一致,代码如下:
@Service
public class UserService {
//定义dao类型属性,不需要添加set方法,添加注入属性注解
@Qualifier(value = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
@Resource:可以根据类型注入,可以根据名称注入
@Resource
是Java扩展包javax的,不是spring的,其余都是spring的
@Service
public class UserService {
// @Resource //根据类型进行注入
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
@Value:注入普通类型属性
@Service
public class UserService {
@Value(value = "abc")// 将"abc"注入到name中,使得name = “abc”
private String name;
public void add() {
System.out.println("service add......."+name);
userDao.add();
}
}
完全注解开发
纯注解开发:配置文件bean1.xml
可以没有,可以把里面的内容<context:component-scan base-package="com.atguigu"></context:component-scan>
放在其他地方,用完全注解的方式开发:
- 创建配置类
src/com/atguigu/spring5/config/SpringConfig.java
,替代xml
配置文件
@Configuration //这个注解会把当前类作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.atguigu"})// @ComponentScan开启组件扫描,basePackages指定扫描的包的路径,如果有多个包用“,”隔开
public class SpringConfig {}
- 编写测试类,使用
AnnotationConfigApplicationContext(配置类)
加载注解配置类
public class TestSpring5Demo1 {
@Test
public void testService2() {
// 加载配置类
// AnnotationConfigApplicationContext:注解配置类
ApplicationContext context
= new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
}
AOP
什么是 AOP:
- 面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
- 使用登录例子说明AOP
登录流程:用户输入用户名密码后,我们去数据库中查询,判断用户名密码是否正确,不正确则重新输入,正确则登陆成功显示主页面。现在我们想在登陆成功后进行权限判断,如果你是管理员我给你显示管理员的页面,如果你是普通用户我给你显示普通用户的页面,如果是以前的话,需要修改原来的代码,但是AOP编程逻辑是新增一个权限控制模块,将其添加到登陆成功但是还没由展示页面这一过程中,以后如果不需要这个功能了,直接将模块移除即可,不会对原来的代码造成很大的影响
底层原理
一、AOP底层使用动态代理
- 有两种情况动态代理
(1)有接口情况,使用JDK动态代理,通过创建接口实现类代理对象来增强类的方法
(2)没有接口情况,使用CGLIB动态代理,通过创建当前类的子类作为代理对象(也就是说这个子类是代理对象)来增强类的方法
JDK动态代理
一、使用JDK动态代理,使用Proxy
类(java.lang.reflect.Proxy
)里面的方法创建代理对象
- 在
Proxy
类中有一个方法调用newProxyInstance
方法来实现JDK动态代理
方法有三个参数:
(1)类加载器
(2)增强方法所在的类,这个类实现的接口,支持多个接口
(3)实现这个接口InvocationHandler
,创建代理对象,写增强的部分
二、编写JDK动态代理代码
- 创建接口,定义方法
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}
- 创建接口实现类,实现方法
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
System.out.println("add方法执行了.....");
return a+b;
}
@Override
public String update(String id) {
System.out.println("update方法执行了.....");
return id;
}
}
- 接下来通过动态代理的方式把
add
方法、update
方法中的功能进行增强(增强的意思就是往里面加一些逻辑,加一些功能):新建src/com/atguigu/spring5/JDKProxy.java
文件,在里面使用Proxy
类创建接口代理对象
public class JDKProxy {
public static void main(String[] args) {
//创建接口实现类代理对象
Class[] interfaces = {UserDao.class};
// 第三个参数可以通过匿名内部类的方式传入对象
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// });
// 不适用匿名内部类
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result:"+result);
}
}
//创建代理对象代码
class UserDaoProxy implements InvocationHandler {
//1 创建的是谁的代理对象,把谁传递过来
//通过有参数构造传递,建的是谁的代理对象,把谁作为参数传给有参构造方法,这里应该传UserDaoImpl,即public UserDaoProxy(UserDaoImpl userDaoImpl)
// 但是由于我们希望这个代理对象更通用,所以传入的是Object,即:public UserDaoProxy(Object obj)
private Object obj;// 代理对象
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//只要UserDaoProxy对象一创建,invoke方法就会被调用,在这个方法中写增强的逻辑。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被增强的方法之前执行 method.getName()获得方法的名字
System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));
//被增强的方法执行,这个方法就是你原来写的方法,是UserDaoImpl中的add,update方法
// method中有个invoke方法,invoke方法接受两个参数,第一个是代理对象,第二个是参数
Object res = method.invoke(obj, args);
//被增强的方法之后执行
System.out.println("方法之后执行...."+obj);
return res;
}
}
AOP操作术语
一、连接点:类里面哪些方法可以被增强,这些方法称为连接点
二、切入点:实际被真正增强的方法,这些方法称为切入点
三、通知(增强):实际增强的逻辑部分称为通知(增强)。通知有多种类型:
- 前置通知:增强方法执行之前执行的方法
- 后置通知:增强方法执行之后执行的方法
- 环绕通知:增强方法执行之前、之后执行的方法
- 异常通知:增强方法执行异常时执行的方法
- 最终通知:不管增强方法是否执行成功,是否有异常都执行的方法
四、切面:使一个动作,把通知应用到切入点这一过程,如把权限判断加到登录流程这一过程就是切面
准备工作
一、Spring框架一般都是基于AspectJ实现AOP操作。AspectJ不是Spring组成部分,而是一个独立AOP框架,一般把AspectJ和Spirng框架一起使用,进行AOP操作
二、基于AspectJ实现AOP操作有两种方式:
- 基于
xml
配置文件实现 - 基于注解方式实现(使用)
三、在项目工程里面引入AOP相关依赖
- 将以下4个jar包复制到
lib
文件中
- 将上述4个jar包导入到项目中
四、切入点表达式 - 切入点表达式作用:知道对哪个类里面的哪个方法进行增强
- 语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
(1)权限修饰符可以为*
,代表任意修饰符
(2)返回类型可以省略
(3)参数列表中的..
表示是方法中的参数
- 对
com.atguigu.dao.BookDao
类里面的add
进行增强:execution(* com.atguigu.dao.BookDao.add(..))
- 对
com.atguigu.dao.BookDao
类里面的所有的方法进行增强:execution(* com.atguigu.dao.BookDao.* (..))
- 对com.atguigu.dao包里面所有类,类里面所有方法进行增强:
execution(* com.atguigu.dao.*.* (..))
基于AspectJ注解实现AOP操作
一、创建类src/com/atguigu/spring5/aopanno/User.java
,在类里面定义方法
//被增强的类
public class User {
public void add() {
System.out.println("add.......");
}
}
二、创建增强类(编写增强逻辑)
(1)新建src/com/atguigu/spring5/aopanno/UserProxy.java
增强类,在里面创建方法,让不同方法代表不同通知类型
//增强的类
public class UserProxy {
public void before() {//前置通知
System.out.println("before......");
}
// 后置通知等通知都可以在后面配置,这里暂不做演示
}
三、进行通知的配置
- 在spring配置文件中,开启注解扫描
(1)配置文件中引入命名空间:
(2)开启注解扫描
<context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>
- 使用注解创建
User
和UserProxy
对象
@Component
public class User {// ...}
@Component
public class UserProxy {}
- 在增强类上面添加注解
@Aspect
,表示要给这个类生成代理对象
@Component
@Aspect //生成代理对象
public class UserProxy {}
- 在spring配置文件中开启生成代理对象:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
,之后spring会到注解扫描指定的包中找类上面有@Aspect
注解的类,他就给这个类生成代理对象
四、配置不同类型的通知:在增强类的里面,在作为通知方法上面添加通知类型注解,并使用切入点表达式来指定要增强的方法
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//前置通知
//@Before 注解表示作为前置通知
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void before() {
System.out.println("before.........");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
//最终通知
@After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void after() {
System.out.println("after.........");
}
//异常通知
@AfterThrowing(value = "execution(*
com.atguigu.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.........");
}
//环绕通知
@Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws
Throwable {
System.out.println("环绕之前.........");
//被增强的方法执行
proceedingJoinPoint.proceed();
System.out.println("环绕之后.........");
}
}
五、测试:新建src/com/atguigu/spring5/test/TestAop.java
,其中代码为:
public class TestAop {
@Test
public void testAopAnno() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);//我们要增强User类中的方法
user.add();
}
}
增强方法没有异常的执行顺序:
增强方法有异常的执行顺序:
相同的切入点抽取
上面在配置不同类型的通知时,使用了相同的切入点,即切入点表达式都一样value = "execution(* com.atguigu.spring5.aopanno.User.add(..))"
,那么我们把相同的切入点表达式提取出来,减少冗余代码
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//相同切入点抽取:随便写一个方法,给方法配置注解@Pointcut(value = "切入点表达式")
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {}
//前置通知
//@Before 注解表示作为前置通知,value的值为执行切入点方法
@Before(value = "pointdemo()")
public void before() {
System.out.println("before.........");
}
// ...
}
有多个增强类对同一个方法进行增强,设置增强类优先级
新建src/com/atguigu/spring5/aopanno/PersonProxy.java
,另外,这个类也想对User
类中的add
方法进行增强,而UserProxy
类也对User
类中的add
方法进行增强,现在我想设置这两个方法的优先级:在增强类上面添加注解@Order(数字类型的值)
,数字类型的值从0开始越小优先级越高。PersonProxy
类中代码如下:
@Component
@Aspect
@Order(1)
public class PersonProxy {
//后置通知(返回通知)
@Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("Person Before.........");
}
}
在User
类上添加注解:@Order(3)
完全使用注解开发
创建配置类src/com/atguigu/spring5/config/ConfigAop.java
,代码如下,不需要创建xml
配置文件
@Configuration// 表明这是个配置类
@ComponentScan(basePackages = {"com.atguigu"})// 开启注解扫描,扫描com.atguigu这个包
@EnableAspectJAutoProxy(proxyTargetClass = true)// 开启Aspect生成代理对象,proxyTargetClass默认为false,我们要手动把他设为true
public class ConfigAop {}
基于AspectJ配置文件方式实现AOP操作
一、创建两个类,增强类src/com/atguigu/spring5/aopxml/BookProxy.java
和被增强类src/com/atguigu/spring5/aopxml/Book.java
,并为他们创建方法:
public class Book {
public void buy() {
System.out.println("buy.............");
}
}
public class BookProxy {
public void before() {
System.out.println("before.........");
}
}
二、在spring配置文件src/bean2.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"
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">
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
<!--配置aop增强-->
<aop:config>
<!--切入点使用切入点表达式指定
id:指定切入点名字,随便写
expression:指定切入点表达式
-->
<aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面 ref指定增强类-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上
aop:具体通知,如aop:before
method:指定增强的方法
pointcut-ref:指定增强方法要作用在哪个切入点上
-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
</beans>